From e0a6ef02df3e5c4713492535932a66772548c61a Mon Sep 17 00:00:00 2001 From: GraceSolutions Date: Wed, 3 Jun 2026 19:59:11 -0400 Subject: [PATCH] M9: bulk + duplicate + connection inheritance - Bulk parameter sets on New-/Update-/Remove-InfisicalSecret via v3/secrets/batch/raw. - Copy-InfisicalSecret cmdlet wrapping v4/secrets/duplicate. - InfisicalCmdletBase.Resolve{ProjectId,Environment,SecretPath,ApiVersion,OrganizationId} with verbose inheritance logging. - All resource cmdlets refactored to use the resolution helpers. - InfisicalBulkSecretConverter for flexible Hashtable -> DTO mapping. - 22 new unit tests covering registry, DTOs, converter, and inheritance helpers. Total: 161 passing. --- CHANGELOG.md | 19 ++ Module/PSInfisicalAPI/PSInfisicalAPI.psd1 | 5 +- Module/PSInfisicalAPI/bin/PSInfisicalAPI.dll | Bin 151552 -> 177152 bytes build.ps1 | 1 + docs/DesignSpec.md | 2 + .../BulkSecretConverterTests.cs | 96 ++++++++ .../BulkSecretDtoTests.cs | 137 +++++++++++ .../CmdletBaseInheritanceTests.cs | 143 ++++++++++++ .../EndpointRegistryTests.cs | 4 + .../Cmdlets/CopyInfisicalSecretCmdlet.cs | 75 ++++++ .../Cmdlets/GetInfisicalEnvironmentCmdlet.cs | 3 +- .../Cmdlets/GetInfisicalEnvironmentsCmdlet.cs | 3 +- .../Cmdlets/GetInfisicalFolderCmdlet.cs | 5 +- .../Cmdlets/GetInfisicalFoldersCmdlet.cs | 5 +- .../Cmdlets/GetInfisicalProjectCmdlet.cs | 5 +- .../Cmdlets/GetInfisicalSecretCmdlet.cs | 8 +- .../Cmdlets/GetInfisicalSecretsCmdlet.cs | 8 +- .../Cmdlets/GetInfisicalTagCmdlet.cs | 3 +- .../Cmdlets/GetInfisicalTagsCmdlet.cs | 3 +- .../Cmdlets/InfisicalCmdletBase.cs | 39 ++++ .../Cmdlets/NewInfisicalEnvironmentCmdlet.cs | 3 +- .../Cmdlets/NewInfisicalFolderCmdlet.cs | 5 +- .../Cmdlets/NewInfisicalSecretCmdlet.cs | 53 ++++- .../Cmdlets/NewInfisicalTagCmdlet.cs | 3 +- .../RemoveInfisicalEnvironmentCmdlet.cs | 3 +- .../Cmdlets/RemoveInfisicalFolderCmdlet.cs | 5 +- .../Cmdlets/RemoveInfisicalProjectCmdlet.cs | 12 +- .../Cmdlets/RemoveInfisicalSecretCmdlet.cs | 50 +++- .../Cmdlets/RemoveInfisicalTagCmdlet.cs | 3 +- .../UpdateInfisicalEnvironmentCmdlet.cs | 3 +- .../Cmdlets/UpdateInfisicalFolderCmdlet.cs | 5 +- .../Cmdlets/UpdateInfisicalProjectCmdlet.cs | 10 +- .../Cmdlets/UpdateInfisicalSecretCmdlet.cs | 52 ++++- .../Cmdlets/UpdateInfisicalTagCmdlet.cs | 3 +- .../Endpoints/InfisicalEndpointNames.cs | 4 + .../Endpoints/InfisicalEndpointRegistry.cs | 48 ++++ .../Secrets/InfisicalBulkSecretConverter.cs | 164 +++++++++++++ .../Secrets/InfisicalSecretDtos.cs | 71 ++++++ .../Secrets/InfisicalSecretQuery.cs | 65 ++++++ .../Secrets/InfisicalSecretsClient.cs | 221 ++++++++++++++++++ 40 files changed, 1282 insertions(+), 65 deletions(-) create mode 100644 src/PSInfisicalAPI.Tests/BulkSecretConverterTests.cs create mode 100644 src/PSInfisicalAPI.Tests/BulkSecretDtoTests.cs create mode 100644 src/PSInfisicalAPI.Tests/CmdletBaseInheritanceTests.cs create mode 100644 src/PSInfisicalAPI/Cmdlets/CopyInfisicalSecretCmdlet.cs create mode 100644 src/PSInfisicalAPI/Secrets/InfisicalBulkSecretConverter.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index dd1a5e3..251ad61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,25 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) loos ## Unreleased +## 2026.06.03.2207 + +- Build produced from commit 09c3d5c68bbc. +- **M9 — Bulk, Duplicate & Inheritance**: + - **Bulk parameter sets** added to `New-InfisicalSecret`, `Update-InfisicalSecret`, and `Remove-InfisicalSecret` accepting `-Secrets Hashtable[]`; client methods `CreateBatch`/`UpdateBatch`/`DeleteBatch` wrap `POST|PATCH|DELETE /api/v3/secrets/batch/raw`. + - **`Copy-InfisicalSecret`** cmdlet added, wrapping `POST /api/v4/secrets/duplicate` with source/destination environment + path parameters and per-attribute copy toggles. + - **Connection inheritance** centralized in `InfisicalCmdletBase` (`ResolveProjectId`/`ResolveEnvironment`/`ResolveSecretPath`/`ResolveApiVersion`/`ResolveOrganizationId`). Explicit parameters always win; missing values fall back to the active connection and emit a `-Verbose` line. + - Project/Environment/Folder/Tag and all secret cmdlets refactored to use the inheritance helpers; existing explicit-parameter behavior is preserved. + - `InfisicalBulkSecretConverter` accepts flexible key aliases (`SecretName`/`Name`/`Key`, `SecretValue`/`Value`, `SecretComment`/`Comment`, `Metadata`/`SecretMetadata`). + - Test count: 161 (up from 139). Added coverage for bulk DTO shapes, the converter, the duplicate request DTO, registry entries for the four new endpoints, and the resolution helpers. + +## Unreleased (carried forward) + +## 2026.06.03.2206 + +- Build produced from commit 09c3d5c68bbc. + +## Unreleased (carried forward) + ## 2026.06.03.2136 - Build produced from commit d9822aab7a4a. diff --git a/Module/PSInfisicalAPI/PSInfisicalAPI.psd1 b/Module/PSInfisicalAPI/PSInfisicalAPI.psd1 index 3e80f15..bd0557e 100644 --- a/Module/PSInfisicalAPI/PSInfisicalAPI.psd1 +++ b/Module/PSInfisicalAPI/PSInfisicalAPI.psd1 @@ -1,6 +1,6 @@ @{ RootModule = 'PSInfisicalAPI.psm1' - ModuleVersion = '2026.06.03.2136' + ModuleVersion = '2026.06.03.2207' GUID = 'b8a2f3d4-7c51-4d2f-9e6a-1f0c8b3d4e51' Author = 'Grace Solutions' CompanyName = 'Grace Solutions' @@ -17,6 +17,7 @@ 'New-InfisicalSecret', 'Update-InfisicalSecret', 'Remove-InfisicalSecret', + 'Copy-InfisicalSecret', 'ConvertTo-InfisicalSecretDictionary', 'Export-InfisicalSecrets', 'Get-InfisicalProjects', @@ -50,7 +51,7 @@ LicenseUri = 'https://www.gnu.org/licenses/agpl-3.0.html' ProjectUri = 'https://prod.git.gracesolution.info/gsadmin/PSInfisicalAPI' ReleaseNotes = 'See CHANGELOG.md in the project repository for release history.' - CommitHash = 'd9822aab7a4a' + CommitHash = '09c3d5c68bbc' } } } \ No newline at end of file diff --git a/Module/PSInfisicalAPI/bin/PSInfisicalAPI.dll b/Module/PSInfisicalAPI/bin/PSInfisicalAPI.dll index 72da4ed07bb9ab1b8de515b598b68715f5e23e56..ca605fc20afc9c2116736db3c473c1507b503b87 100644 GIT binary patch literal 177152 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 (1Nx7WEiHgJ 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

Xp`fH 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

kW9nY7!cDCDgRf}bIu3g~VEqE2xT)Wu0 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 diff --git a/build.ps1 b/build.ps1 index c1457a1..6a0066d 100644 --- a/build.ps1 +++ b/build.ps1 @@ -105,6 +105,7 @@ function Write-Manifest { 'New-InfisicalSecret', 'Update-InfisicalSecret', 'Remove-InfisicalSecret', + 'Copy-InfisicalSecret', 'ConvertTo-InfisicalSecretDictionary', 'Export-InfisicalSecrets', 'Get-InfisicalProjects', diff --git a/docs/DesignSpec.md b/docs/DesignSpec.md index 8b1a552..a7124fd 100644 --- a/docs/DesignSpec.md +++ b/docs/DesignSpec.md @@ -16,6 +16,7 @@ Get-InfisicalSecret New-InfisicalSecret Update-InfisicalSecret Remove-InfisicalSecret +Copy-InfisicalSecret ConvertTo-InfisicalSecretDictionary Export-InfisicalSecrets Get-InfisicalProjects @@ -224,6 +225,7 @@ Example shape: 'New-InfisicalSecret', 'Update-InfisicalSecret', 'Remove-InfisicalSecret', + 'Copy-InfisicalSecret', 'ConvertTo-InfisicalSecretDictionary', 'Export-InfisicalSecrets', 'Get-InfisicalProjects', diff --git a/src/PSInfisicalAPI.Tests/BulkSecretConverterTests.cs b/src/PSInfisicalAPI.Tests/BulkSecretConverterTests.cs new file mode 100644 index 0000000..7972862 --- /dev/null +++ b/src/PSInfisicalAPI.Tests/BulkSecretConverterTests.cs @@ -0,0 +1,96 @@ +using System.Collections; +using PSInfisicalAPI.Errors; +using PSInfisicalAPI.Secrets; +using Xunit; + +namespace PSInfisicalAPI.Tests +{ + public class BulkSecretConverterTests + { + [Fact] + public void ToCreateItems_Maps_Standard_Keys() + { + Hashtable entry = new Hashtable + { + { "SecretName", "API_KEY" }, + { "SecretValue", "abc" }, + { "SecretComment", "primary" }, + { "SkipMultilineEncoding", true }, + { "TagIds", new[] { "tag-1", "tag-2" } } + }; + + InfisicalBulkCreateSecretItem[] items = InfisicalBulkSecretConverter.ToCreateItems(new[] { entry }); + Assert.Single(items); + Assert.Equal("API_KEY", items[0].SecretName); + Assert.Equal("abc", items[0].SecretValue); + Assert.Equal("primary", items[0].SecretComment); + Assert.True(items[0].SkipMultilineEncoding); + Assert.Equal(new[] { "tag-1", "tag-2" }, items[0].TagIds); + } + + [Fact] + public void ToCreateItems_Accepts_Name_Alias_For_SecretName() + { + Hashtable entry = new Hashtable + { + { "Name", "API_KEY" }, + { "Value", "abc" } + }; + + InfisicalBulkCreateSecretItem[] items = InfisicalBulkSecretConverter.ToCreateItems(new[] { entry }); + Assert.Single(items); + Assert.Equal("API_KEY", items[0].SecretName); + Assert.Equal("abc", items[0].SecretValue); + } + + [Fact] + public void ToCreateItems_Without_SecretName_Throws() + { + Hashtable entry = new Hashtable { { "Value", "abc" } }; + + Assert.Throws(() => + InfisicalBulkSecretConverter.ToCreateItems(new[] { entry })); + } + + [Fact] + public void ToUpdateItems_Maps_NewSecretName() + { + Hashtable entry = new Hashtable + { + { "SecretName", "API_KEY" }, + { "NewSecretName", "RENAMED" }, + { "SecretValue", "new-value" } + }; + + InfisicalBulkUpdateSecretItem[] items = InfisicalBulkSecretConverter.ToUpdateItems(new[] { entry }); + Assert.Single(items); + Assert.Equal("API_KEY", items[0].SecretName); + Assert.Equal("RENAMED", items[0].NewSecretName); + Assert.Equal("new-value", items[0].SecretValue); + } + + [Fact] + public void ToCreateItems_Maps_Metadata_Dictionary() + { + Hashtable meta = new Hashtable { { "owner", "platform" }, { "tier", "1" } }; + Hashtable entry = new Hashtable + { + { "SecretName", "API_KEY" }, + { "SecretValue", "abc" }, + { "Metadata", meta } + }; + + InfisicalBulkCreateSecretItem[] items = InfisicalBulkSecretConverter.ToCreateItems(new[] { entry }); + Assert.NotNull(items[0].SecretMetadata); + Assert.Equal("platform", items[0].SecretMetadata["owner"]); + Assert.Equal("1", items[0].SecretMetadata["tier"]); + } + + [Fact] + public void ToCreateItems_Empty_Input_Returns_Empty() + { + InfisicalBulkCreateSecretItem[] items = InfisicalBulkSecretConverter.ToCreateItems(null); + Assert.Empty(items); + } + } +} diff --git a/src/PSInfisicalAPI.Tests/BulkSecretDtoTests.cs b/src/PSInfisicalAPI.Tests/BulkSecretDtoTests.cs new file mode 100644 index 0000000..b4a4474 --- /dev/null +++ b/src/PSInfisicalAPI.Tests/BulkSecretDtoTests.cs @@ -0,0 +1,137 @@ +using System.Collections.Generic; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace PSInfisicalAPI.Tests +{ + public class BulkSecretDtoTests + { + 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 BatchCreateItem_Serializes_With_Expected_Field_Names() + { + object item = MakeDto("PSInfisicalAPI.Secrets.InfisicalSecretBatchCreateItemDto"); + item.GetType().GetProperty("SecretKey").SetValue(item, "API_KEY"); + item.GetType().GetProperty("SecretValue").SetValue(item, "abc"); + + JObject json = JObject.Parse(JsonConvert.SerializeObject(item)); + Assert.Equal("API_KEY", (string)json["secretKey"]); + Assert.Equal("abc", (string)json["secretValue"]); + Assert.False(json.ContainsKey("skipMultilineEncoding")); + Assert.False(json.ContainsKey("tagIds")); + Assert.False(json.ContainsKey("secretMetadata")); + } + + [Fact] + public void BatchUpdateItem_Omits_Null_Optional_Fields() + { + object item = MakeDto("PSInfisicalAPI.Secrets.InfisicalSecretBatchUpdateItemDto"); + item.GetType().GetProperty("SecretKey").SetValue(item, "API_KEY"); + item.GetType().GetProperty("NewSecretName").SetValue(item, "RENAMED"); + + JObject json = JObject.Parse(JsonConvert.SerializeObject(item)); + Assert.Equal("API_KEY", (string)json["secretKey"]); + Assert.Equal("RENAMED", (string)json["newSecretName"]); + Assert.False(json.ContainsKey("secretValue")); + Assert.False(json.ContainsKey("secretComment")); + } + + [Fact] + public void BatchCreateRequest_Serializes_With_Expected_Envelope() + { + System.Type itemType = ModuleAssembly.GetType("PSInfisicalAPI.Secrets.InfisicalSecretBatchCreateItemDto", true); + object item = System.Activator.CreateInstance(itemType); + itemType.GetProperty("SecretKey").SetValue(item, "K1"); + itemType.GetProperty("SecretValue").SetValue(item, "V1"); + + System.Type listType = typeof(List<>).MakeGenericType(itemType); + object list = System.Activator.CreateInstance(listType); + listType.GetMethod("Add").Invoke(list, new object[] { item }); + + object dto = MakeDto("PSInfisicalAPI.Secrets.InfisicalSecretBatchCreateRequestDto"); + 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("Secrets").SetValue(dto, list); + + 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"]); + JArray secrets = (JArray)json["secrets"]; + Assert.Single(secrets); + Assert.Equal("K1", (string)secrets[0]["secretKey"]); + Assert.Equal("V1", (string)secrets[0]["secretValue"]); + } + + [Fact] + public void BatchDeleteRequest_Serializes_With_Secret_Keys() + { + System.Type itemType = ModuleAssembly.GetType("PSInfisicalAPI.Secrets.InfisicalSecretBatchDeleteItemDto", true); + object item = System.Activator.CreateInstance(itemType); + itemType.GetProperty("SecretKey").SetValue(item, "K1"); + + System.Type listType = typeof(List<>).MakeGenericType(itemType); + object list = System.Activator.CreateInstance(listType); + listType.GetMethod("Add").Invoke(list, new object[] { item }); + + object dto = MakeDto("PSInfisicalAPI.Secrets.InfisicalSecretBatchDeleteRequestDto"); + dto.GetType().GetProperty("WorkspaceId").SetValue(dto, "wks-1"); + dto.GetType().GetProperty("Environment").SetValue(dto, "prod"); + dto.GetType().GetProperty("Secrets").SetValue(dto, list); + + JObject json = JObject.Parse(JsonConvert.SerializeObject(dto)); + Assert.Equal("wks-1", (string)json["workspaceId"]); + JArray secrets = (JArray)json["secrets"]; + Assert.Single(secrets); + Assert.Equal("K1", (string)secrets[0]["secretKey"]); + } + + [Fact] + public void DuplicateRequest_Serializes_With_Expected_Field_Names() + { + object dto = MakeDto("PSInfisicalAPI.Secrets.InfisicalSecretDuplicateRequestDto"); + dto.GetType().GetProperty("ProjectId").SetValue(dto, "wks-1"); + dto.GetType().GetProperty("SourceEnvironment").SetValue(dto, "dev"); + dto.GetType().GetProperty("DestinationEnvironment").SetValue(dto, "prod"); + dto.GetType().GetProperty("SourceSecretPath").SetValue(dto, "/db"); + dto.GetType().GetProperty("DestinationSecretPath").SetValue(dto, "/db"); + dto.GetType().GetProperty("SecretIds").SetValue(dto, new[] { "id-1", "id-2" }); + dto.GetType().GetProperty("OverwriteExisting").SetValue(dto, true); + + JObject json = JObject.Parse(JsonConvert.SerializeObject(dto)); + Assert.Equal("wks-1", (string)json["projectId"]); + Assert.Equal("dev", (string)json["sourceEnvironment"]); + Assert.Equal("prod", (string)json["destinationEnvironment"]); + Assert.Equal("/db", (string)json["sourceSecretPath"]); + Assert.Equal("/db", (string)json["destinationSecretPath"]); + Assert.True((bool)json["overwriteExisting"]); + JArray ids = (JArray)json["secretIds"]; + Assert.Equal(2, ids.Count); + Assert.Equal("id-1", (string)ids[0]); + Assert.False(json.ContainsKey("attributesToCopy")); + } + + [Fact] + public void DuplicateAttributes_Omits_Null_Toggles() + { + object attrs = MakeDto("PSInfisicalAPI.Secrets.InfisicalSecretDuplicateAttributesDto"); + attrs.GetType().GetProperty("SecretValue").SetValue(attrs, true); + + JObject json = JObject.Parse(JsonConvert.SerializeObject(attrs)); + Assert.True((bool)json["secretValue"]); + Assert.False(json.ContainsKey("secretComment")); + Assert.False(json.ContainsKey("tags")); + Assert.False(json.ContainsKey("metadata")); + } + } +} diff --git a/src/PSInfisicalAPI.Tests/CmdletBaseInheritanceTests.cs b/src/PSInfisicalAPI.Tests/CmdletBaseInheritanceTests.cs new file mode 100644 index 0000000..6ae42cd --- /dev/null +++ b/src/PSInfisicalAPI.Tests/CmdletBaseInheritanceTests.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.Management.Automation; +using System.Reflection; +using PSInfisicalAPI.Cmdlets; +using PSInfisicalAPI.Connections; +using PSInfisicalAPI.Logging; +using PSInfisicalAPI.Models; +using Xunit; + +namespace PSInfisicalAPI.Tests +{ + public class CmdletBaseInheritanceTests + { + private sealed class RecordingLogger : IInfisicalLogger + { + public List VerboseEntries { get; } = new List(); + + public void Information(string component, string message) { } + public void Verbose(string component, string message) { VerboseEntries.Add(message); } + public void Debug(string component, string message) { } + public void Warning(string component, string message) { } + public void Error(string component, string message) { } + } + + [Cmdlet(VerbsCommon.Get, "TestCmdlet")] + private sealed class TestCmdlet : InfisicalCmdletBase + { + public string CallResolveProjectId(InfisicalConnection connection, string explicitValue) + { + return ResolveProjectId(connection, explicitValue); + } + + public string CallResolveEnvironment(InfisicalConnection connection, string explicitValue) + { + return ResolveEnvironment(connection, explicitValue); + } + + public string CallResolveSecretPath(InfisicalConnection connection, string explicitValue) + { + return ResolveSecretPath(connection, explicitValue); + } + + public string CallResolveApiVersion(InfisicalConnection connection, string explicitValue) + { + return ResolveApiVersion(connection, explicitValue); + } + + public string CallResolveOrganizationId(InfisicalConnection connection, string explicitValue) + { + return ResolveOrganizationId(connection, explicitValue); + } + } + + private static TestCmdlet CreateCmdletWith(RecordingLogger logger) + { + TestCmdlet cmdlet = new TestCmdlet(); + FieldInfo field = typeof(InfisicalCmdletBase).GetField("_logger", BindingFlags.NonPublic | BindingFlags.Instance); + field.SetValue(cmdlet, logger); + return cmdlet; + } + + private static InfisicalConnection ConnectionWithDefaults() + { + return new InfisicalConnection + { + BaseUri = new Uri("https://app.example.com"), + ProjectId = "proj-conn", + Environment = "prod-conn", + DefaultSecretPath = "/db", + OrganizationId = "org-conn", + PinnedApiVersion = "v3" + }; + } + + [Fact] + public void Explicit_Value_Overrides_Connection_And_Does_Not_Log() + { + RecordingLogger logger = new RecordingLogger(); + TestCmdlet cmdlet = CreateCmdletWith(logger); + + string resolved = cmdlet.CallResolveProjectId(ConnectionWithDefaults(), "explicit-proj"); + Assert.Equal("explicit-proj", resolved); + Assert.Empty(logger.VerboseEntries); + } + + [Fact] + public void Missing_Value_Inherits_From_Connection_And_Logs() + { + RecordingLogger logger = new RecordingLogger(); + TestCmdlet cmdlet = CreateCmdletWith(logger); + + string resolved = cmdlet.CallResolveProjectId(ConnectionWithDefaults(), null); + Assert.Equal("proj-conn", resolved); + Assert.Single(logger.VerboseEntries); + Assert.Contains("Inherited ProjectId", logger.VerboseEntries[0]); + Assert.Contains("proj-conn", logger.VerboseEntries[0]); + } + + [Fact] + public void ResolveSecretPath_Defaults_To_Root_When_Connection_Has_No_Default() + { + RecordingLogger logger = new RecordingLogger(); + TestCmdlet cmdlet = CreateCmdletWith(logger); + + InfisicalConnection bareConnection = new InfisicalConnection { BaseUri = new Uri("https://app.example.com") }; + string resolved = cmdlet.CallResolveSecretPath(bareConnection, null); + Assert.Equal("/", resolved); + } + + [Fact] + public void ResolveSecretPath_Inherits_From_Connection_When_Set() + { + RecordingLogger logger = new RecordingLogger(); + TestCmdlet cmdlet = CreateCmdletWith(logger); + + string resolved = cmdlet.CallResolveSecretPath(ConnectionWithDefaults(), null); + Assert.Equal("/db", resolved); + Assert.Contains(logger.VerboseEntries, v => v.Contains("SecretPath") && v.Contains("/db")); + } + + [Fact] + public void ResolveApiVersion_Prefers_PinnedApiVersion_From_Connection() + { + RecordingLogger logger = new RecordingLogger(); + TestCmdlet cmdlet = CreateCmdletWith(logger); + + string resolved = cmdlet.CallResolveApiVersion(ConnectionWithDefaults(), null); + Assert.Equal("v3", resolved); + } + + [Fact] + public void ResolveEnvironment_And_ResolveOrganizationId_Inherit() + { + RecordingLogger logger = new RecordingLogger(); + TestCmdlet cmdlet = CreateCmdletWith(logger); + + Assert.Equal("prod-conn", cmdlet.CallResolveEnvironment(ConnectionWithDefaults(), null)); + Assert.Equal("org-conn", cmdlet.CallResolveOrganizationId(ConnectionWithDefaults(), null)); + Assert.Equal(2, logger.VerboseEntries.Count); + } + } +} diff --git a/src/PSInfisicalAPI.Tests/EndpointRegistryTests.cs b/src/PSInfisicalAPI.Tests/EndpointRegistryTests.cs index adeccf3..d931695 100644 --- a/src/PSInfisicalAPI.Tests/EndpointRegistryTests.cs +++ b/src/PSInfisicalAPI.Tests/EndpointRegistryTests.cs @@ -71,6 +71,10 @@ namespace PSInfisicalAPI.Tests [InlineData(InfisicalEndpointNames.LdapAuthLogin, "POST", "/api/v1/auth/ldap-auth/login")] [InlineData(InfisicalEndpointNames.AzureAuthLogin, "POST", "/api/v1/auth/azure-auth/login")] [InlineData(InfisicalEndpointNames.GcpIamAuthLogin, "POST", "/api/v1/auth/gcp-auth/login")] + [InlineData(InfisicalEndpointNames.BulkCreateSecret, "POST", "/api/v3/secrets/batch/raw")] + [InlineData(InfisicalEndpointNames.BulkUpdateSecret, "PATCH", "/api/v3/secrets/batch/raw")] + [InlineData(InfisicalEndpointNames.BulkDeleteSecret, "DELETE", "/api/v3/secrets/batch/raw")] + [InlineData(InfisicalEndpointNames.DuplicateSecret, "POST", "/api/v4/secrets/duplicate")] public void Registered_Endpoints_Have_Expected_Shape(string name, string method, string template) { InfisicalEndpointDefinition definition = InfisicalEndpointRegistry.Get(name); diff --git a/src/PSInfisicalAPI/Cmdlets/CopyInfisicalSecretCmdlet.cs b/src/PSInfisicalAPI/Cmdlets/CopyInfisicalSecretCmdlet.cs new file mode 100644 index 0000000..ce4baea --- /dev/null +++ b/src/PSInfisicalAPI/Cmdlets/CopyInfisicalSecretCmdlet.cs @@ -0,0 +1,75 @@ +using System; +using System.Management.Automation; +using PSInfisicalAPI.Connections; +using PSInfisicalAPI.Models; +using PSInfisicalAPI.Secrets; + +namespace PSInfisicalAPI.Cmdlets +{ + [Cmdlet(VerbsCommon.Copy, "InfisicalSecret", SupportsShouldProcess = true)] + [OutputType(typeof(InfisicalSecret))] + public sealed class CopyInfisicalSecretCmdlet : InfisicalCmdletBase + { + [Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] + [Alias("Id", "SecretIds")] + public string[] SecretId { get; set; } + + [Parameter(Mandatory = true)] + public string DestinationEnvironment { get; set; } + + [Parameter] public string DestinationSecretPath { get; set; } + [Parameter] public string SourceEnvironment { get; set; } + [Parameter] public string SourceSecretPath { get; set; } + [Parameter] public string ProjectId { get; set; } + [Parameter] public string ApiVersion { get; set; } + [Parameter] public SwitchParameter OverwriteExisting { get; set; } + [Parameter] public SwitchParameter CopySecretValue { get; set; } + [Parameter] public SwitchParameter CopySecretComment { get; set; } + [Parameter] public SwitchParameter CopyTags { get; set; } + [Parameter] public SwitchParameter CopyMetadata { get; set; } + + protected override void ProcessRecord() + { + try + { + if (SecretId == null || SecretId.Length == 0) { return; } + + InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); + string resolvedProjectId = ResolveProjectId(connection, ProjectId); + string resolvedSourceEnv = ResolveEnvironment(connection, SourceEnvironment); + string resolvedSourcePath = ResolveSecretPath(connection, SourceSecretPath); + string resolvedApiVersion = ResolveApiVersion(connection, ApiVersion); + + string target = string.Concat(SecretId.Length, " secret(s) -> ", DestinationEnvironment); + if (!ShouldProcess(target, "Duplicate Infisical secrets")) { return; } + + InfisicalDuplicateSecretsRequest request = new InfisicalDuplicateSecretsRequest + { + ProjectId = resolvedProjectId, + SourceEnvironment = resolvedSourceEnv, + DestinationEnvironment = DestinationEnvironment, + SourceSecretPath = resolvedSourcePath, + DestinationSecretPath = DestinationSecretPath, + SecretIds = SecretId, + ApiVersion = resolvedApiVersion, + OverwriteExisting = OverwriteExisting.IsPresent ? (bool?)true : null, + CopySecretValue = CopySecretValue.IsPresent ? (bool?)true : null, + CopySecretComment = CopySecretComment.IsPresent ? (bool?)true : null, + CopyTags = CopyTags.IsPresent ? (bool?)true : null, + CopyMetadata = CopyMetadata.IsPresent ? (bool?)true : null + }; + + InfisicalSecretsClient client = new InfisicalSecretsClient(HttpClient, Logger); + InfisicalSecret[] duplicated = client.Duplicate(connection, request); + if (duplicated != null) + { + foreach (InfisicalSecret secret in duplicated) { WriteObject(secret); } + } + } + catch (Exception exception) + { + ThrowTerminatingForException("CopyInfisicalSecretCmdlet", "DuplicateSecrets", exception); + } + } + } +} diff --git a/src/PSInfisicalAPI/Cmdlets/GetInfisicalEnvironmentCmdlet.cs b/src/PSInfisicalAPI/Cmdlets/GetInfisicalEnvironmentCmdlet.cs index e7f3d59..728ec32 100644 --- a/src/PSInfisicalAPI/Cmdlets/GetInfisicalEnvironmentCmdlet.cs +++ b/src/PSInfisicalAPI/Cmdlets/GetInfisicalEnvironmentCmdlet.cs @@ -21,8 +21,9 @@ namespace PSInfisicalAPI.Cmdlets try { InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); + string resolvedProjectId = ResolveProjectId(connection, ProjectId); InfisicalEnvironmentClient client = new InfisicalEnvironmentClient(HttpClient, Logger); - InfisicalEnvironment env = client.Retrieve(connection, ProjectId, EnvironmentSlugOrId); + InfisicalEnvironment env = client.Retrieve(connection, resolvedProjectId, EnvironmentSlugOrId); if (env != null) { WriteObject(env); diff --git a/src/PSInfisicalAPI/Cmdlets/GetInfisicalEnvironmentsCmdlet.cs b/src/PSInfisicalAPI/Cmdlets/GetInfisicalEnvironmentsCmdlet.cs index 23879d9..2128cda 100644 --- a/src/PSInfisicalAPI/Cmdlets/GetInfisicalEnvironmentsCmdlet.cs +++ b/src/PSInfisicalAPI/Cmdlets/GetInfisicalEnvironmentsCmdlet.cs @@ -17,8 +17,9 @@ namespace PSInfisicalAPI.Cmdlets try { InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); + string resolvedProjectId = ResolveProjectId(connection, ProjectId); InfisicalEnvironmentClient client = new InfisicalEnvironmentClient(HttpClient, Logger); - InfisicalEnvironment[] envs = client.List(connection, ProjectId); + InfisicalEnvironment[] envs = client.List(connection, resolvedProjectId); foreach (InfisicalEnvironment env in envs) { WriteObject(env); diff --git a/src/PSInfisicalAPI/Cmdlets/GetInfisicalFolderCmdlet.cs b/src/PSInfisicalAPI/Cmdlets/GetInfisicalFolderCmdlet.cs index 30da2af..22ff5ee 100644 --- a/src/PSInfisicalAPI/Cmdlets/GetInfisicalFolderCmdlet.cs +++ b/src/PSInfisicalAPI/Cmdlets/GetInfisicalFolderCmdlet.cs @@ -23,8 +23,11 @@ namespace PSInfisicalAPI.Cmdlets try { InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); + string resolvedProjectId = ResolveProjectId(connection, ProjectId); + string resolvedEnvironment = ResolveEnvironment(connection, Environment); + string resolvedPath = ResolveSecretPath(connection, Path); InfisicalFolderClient client = new InfisicalFolderClient(HttpClient, Logger); - InfisicalFolder folder = client.Retrieve(connection, ProjectId, Environment, Path, FolderNameOrId); + InfisicalFolder folder = client.Retrieve(connection, resolvedProjectId, resolvedEnvironment, resolvedPath, FolderNameOrId); if (folder != null) { WriteObject(folder); diff --git a/src/PSInfisicalAPI/Cmdlets/GetInfisicalFoldersCmdlet.cs b/src/PSInfisicalAPI/Cmdlets/GetInfisicalFoldersCmdlet.cs index 7c64a2b..4c3b2a6 100644 --- a/src/PSInfisicalAPI/Cmdlets/GetInfisicalFoldersCmdlet.cs +++ b/src/PSInfisicalAPI/Cmdlets/GetInfisicalFoldersCmdlet.cs @@ -19,8 +19,11 @@ namespace PSInfisicalAPI.Cmdlets try { InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); + string resolvedProjectId = ResolveProjectId(connection, ProjectId); + string resolvedEnvironment = ResolveEnvironment(connection, Environment); + string resolvedPath = ResolveSecretPath(connection, Path); InfisicalFolderClient client = new InfisicalFolderClient(HttpClient, Logger); - InfisicalFolder[] folders = client.List(connection, ProjectId, Environment, Path); + InfisicalFolder[] folders = client.List(connection, resolvedProjectId, resolvedEnvironment, resolvedPath); foreach (InfisicalFolder folder in folders) { WriteObject(folder); diff --git a/src/PSInfisicalAPI/Cmdlets/GetInfisicalProjectCmdlet.cs b/src/PSInfisicalAPI/Cmdlets/GetInfisicalProjectCmdlet.cs index 979928d..93ec71e 100644 --- a/src/PSInfisicalAPI/Cmdlets/GetInfisicalProjectCmdlet.cs +++ b/src/PSInfisicalAPI/Cmdlets/GetInfisicalProjectCmdlet.cs @@ -10,7 +10,7 @@ namespace PSInfisicalAPI.Cmdlets [OutputType(typeof(InfisicalProject))] public sealed class GetInfisicalProjectCmdlet : InfisicalCmdletBase { - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0)] + [Parameter(ValueFromPipelineByPropertyName = true, Position = 0)] [Alias("Id")] public string ProjectId { get; set; } @@ -19,8 +19,9 @@ namespace PSInfisicalAPI.Cmdlets try { InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); + string resolvedProjectId = ResolveProjectId(connection, ProjectId); InfisicalProjectClient client = new InfisicalProjectClient(HttpClient, Logger); - InfisicalProject project = client.Retrieve(connection, ProjectId); + InfisicalProject project = client.Retrieve(connection, resolvedProjectId); if (project != null) { WriteObject(project); diff --git a/src/PSInfisicalAPI/Cmdlets/GetInfisicalSecretCmdlet.cs b/src/PSInfisicalAPI/Cmdlets/GetInfisicalSecretCmdlet.cs index eae5ea5..2493296 100644 --- a/src/PSInfisicalAPI/Cmdlets/GetInfisicalSecretCmdlet.cs +++ b/src/PSInfisicalAPI/Cmdlets/GetInfisicalSecretCmdlet.cs @@ -32,10 +32,10 @@ namespace PSInfisicalAPI.Cmdlets InfisicalRetrieveSecretQuery query = new InfisicalRetrieveSecretQuery { SecretName = SecretName, - ProjectId = ProjectId, - Environment = Environment, - SecretPath = SecretPath, - ApiVersion = ApiVersion, + ProjectId = ResolveProjectId(connection, ProjectId), + Environment = ResolveEnvironment(connection, Environment), + SecretPath = ResolveSecretPath(connection, SecretPath), + ApiVersion = ResolveApiVersion(connection, ApiVersion), Version = Version, Type = Type.ToString(), ViewSecretValue = ViewSecretValue.IsPresent, diff --git a/src/PSInfisicalAPI/Cmdlets/GetInfisicalSecretsCmdlet.cs b/src/PSInfisicalAPI/Cmdlets/GetInfisicalSecretsCmdlet.cs index e3e60bb..599b8a9 100644 --- a/src/PSInfisicalAPI/Cmdlets/GetInfisicalSecretsCmdlet.cs +++ b/src/PSInfisicalAPI/Cmdlets/GetInfisicalSecretsCmdlet.cs @@ -32,10 +32,10 @@ namespace PSInfisicalAPI.Cmdlets InfisicalListSecretsQuery query = new InfisicalListSecretsQuery { - ProjectId = ProjectId, - Environment = Environment, - SecretPath = SecretPath, - ApiVersion = ApiVersion, + ProjectId = ResolveProjectId(connection, ProjectId), + Environment = ResolveEnvironment(connection, Environment), + SecretPath = ResolveSecretPath(connection, SecretPath), + ApiVersion = ResolveApiVersion(connection, ApiVersion), Recursive = Recursive.IsPresent, IncludeImports = IncludeImports.IsPresent, IncludePersonalOverrides = IncludePersonalOverrides.IsPresent, diff --git a/src/PSInfisicalAPI/Cmdlets/GetInfisicalTagCmdlet.cs b/src/PSInfisicalAPI/Cmdlets/GetInfisicalTagCmdlet.cs index cc8d32e..8c7837f 100644 --- a/src/PSInfisicalAPI/Cmdlets/GetInfisicalTagCmdlet.cs +++ b/src/PSInfisicalAPI/Cmdlets/GetInfisicalTagCmdlet.cs @@ -21,8 +21,9 @@ namespace PSInfisicalAPI.Cmdlets try { InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); + string resolvedProjectId = ResolveProjectId(connection, ProjectId); InfisicalTagClient client = new InfisicalTagClient(HttpClient, Logger); - InfisicalTag tag = client.Retrieve(connection, ProjectId, TagSlugOrId); + InfisicalTag tag = client.Retrieve(connection, resolvedProjectId, TagSlugOrId); if (tag != null) { WriteObject(tag); diff --git a/src/PSInfisicalAPI/Cmdlets/GetInfisicalTagsCmdlet.cs b/src/PSInfisicalAPI/Cmdlets/GetInfisicalTagsCmdlet.cs index 8b10649..a4b736c 100644 --- a/src/PSInfisicalAPI/Cmdlets/GetInfisicalTagsCmdlet.cs +++ b/src/PSInfisicalAPI/Cmdlets/GetInfisicalTagsCmdlet.cs @@ -17,8 +17,9 @@ namespace PSInfisicalAPI.Cmdlets try { InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); + string resolvedProjectId = ResolveProjectId(connection, ProjectId); InfisicalTagClient client = new InfisicalTagClient(HttpClient, Logger); - InfisicalTag[] tags = client.List(connection, ProjectId); + InfisicalTag[] tags = client.List(connection, resolvedProjectId); foreach (InfisicalTag tag in tags) { WriteObject(tag); diff --git a/src/PSInfisicalAPI/Cmdlets/InfisicalCmdletBase.cs b/src/PSInfisicalAPI/Cmdlets/InfisicalCmdletBase.cs index fd4f21a..4a3c37b 100644 --- a/src/PSInfisicalAPI/Cmdlets/InfisicalCmdletBase.cs +++ b/src/PSInfisicalAPI/Cmdlets/InfisicalCmdletBase.cs @@ -1,5 +1,6 @@ using System; using System.Management.Automation; +using PSInfisicalAPI.Connections; using PSInfisicalAPI.Errors; using PSInfisicalAPI.Http; using PSInfisicalAPI.Logging; @@ -44,5 +45,43 @@ namespace PSInfisicalAPI.Cmdlets ErrorRecord record = InfisicalErrorHandler.ToErrorRecord(exception, details); ThrowTerminatingError(record); } + + protected string ResolveProjectId(InfisicalConnection connection, string explicitValue) + { + return ResolveValue("ProjectId", explicitValue, connection != null ? connection.ProjectId : null, null); + } + + protected string ResolveEnvironment(InfisicalConnection connection, string explicitValue) + { + return ResolveValue("Environment", explicitValue, connection != null ? connection.Environment : null, null); + } + + protected string ResolveSecretPath(InfisicalConnection connection, string explicitValue) + { + return ResolveValue("SecretPath", explicitValue, connection != null ? connection.DefaultSecretPath : null, "/"); + } + + protected string ResolveApiVersion(InfisicalConnection connection, string explicitValue) + { + string fromConnection = connection != null ? (!string.IsNullOrEmpty(connection.PinnedApiVersion) ? connection.PinnedApiVersion : connection.ApiVersion) : null; + return ResolveValue("ApiVersion", explicitValue, fromConnection, null); + } + + protected string ResolveOrganizationId(InfisicalConnection connection, string explicitValue) + { + return ResolveValue("OrganizationId", explicitValue, connection != null ? connection.OrganizationId : null, null); + } + + private string ResolveValue(string parameterName, string explicitValue, string inheritedValue, string defaultValue) + { + if (!string.IsNullOrEmpty(explicitValue)) { return explicitValue; } + if (!string.IsNullOrEmpty(inheritedValue)) + { + Logger.Verbose(GetType().Name, string.Concat("Inherited ", parameterName, " '", inheritedValue, "' from connection.")); + return inheritedValue; + } + + return defaultValue; + } } } diff --git a/src/PSInfisicalAPI/Cmdlets/NewInfisicalEnvironmentCmdlet.cs b/src/PSInfisicalAPI/Cmdlets/NewInfisicalEnvironmentCmdlet.cs index bcda139..6a00664 100644 --- a/src/PSInfisicalAPI/Cmdlets/NewInfisicalEnvironmentCmdlet.cs +++ b/src/PSInfisicalAPI/Cmdlets/NewInfisicalEnvironmentCmdlet.cs @@ -25,8 +25,9 @@ namespace PSInfisicalAPI.Cmdlets } InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); + string resolvedProjectId = ResolveProjectId(connection, ProjectId); InfisicalEnvironmentClient client = new InfisicalEnvironmentClient(HttpClient, Logger); - InfisicalEnvironment env = client.Create(connection, ProjectId, Name, Slug, Position); + InfisicalEnvironment env = client.Create(connection, resolvedProjectId, Name, Slug, Position); if (env != null) { WriteObject(env); diff --git a/src/PSInfisicalAPI/Cmdlets/NewInfisicalFolderCmdlet.cs b/src/PSInfisicalAPI/Cmdlets/NewInfisicalFolderCmdlet.cs index ac1d044..f31ff1d 100644 --- a/src/PSInfisicalAPI/Cmdlets/NewInfisicalFolderCmdlet.cs +++ b/src/PSInfisicalAPI/Cmdlets/NewInfisicalFolderCmdlet.cs @@ -25,8 +25,11 @@ namespace PSInfisicalAPI.Cmdlets } InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); + string resolvedProjectId = ResolveProjectId(connection, ProjectId); + string resolvedEnvironment = ResolveEnvironment(connection, Environment); + string resolvedPath = ResolveSecretPath(connection, Path); InfisicalFolderClient client = new InfisicalFolderClient(HttpClient, Logger); - InfisicalFolder folder = client.Create(connection, ProjectId, Environment, Name, Path); + InfisicalFolder folder = client.Create(connection, resolvedProjectId, resolvedEnvironment, Name, resolvedPath); if (folder != null) { WriteObject(folder); diff --git a/src/PSInfisicalAPI/Cmdlets/NewInfisicalSecretCmdlet.cs b/src/PSInfisicalAPI/Cmdlets/NewInfisicalSecretCmdlet.cs index 4dd0e82..2935f2e 100644 --- a/src/PSInfisicalAPI/Cmdlets/NewInfisicalSecretCmdlet.cs +++ b/src/PSInfisicalAPI/Cmdlets/NewInfisicalSecretCmdlet.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Management.Automation; using System.Security; using PSInfisicalAPI.Connections; @@ -12,7 +13,9 @@ namespace PSInfisicalAPI.Cmdlets [OutputType(typeof(InfisicalSecret))] public sealed class NewInfisicalSecretCmdlet : InfisicalCmdletBase { - [Parameter(Mandatory = true, Position = 0)] public string SecretName { get; set; } + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "PlainText")] + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "SecureString")] + public string SecretName { get; set; } [Parameter(Mandatory = true, Position = 1, ParameterSetName = "PlainText")] public string SecretValue { get; set; } @@ -20,6 +23,9 @@ namespace PSInfisicalAPI.Cmdlets [Parameter(Mandatory = true, Position = 1, ParameterSetName = "SecureString")] public SecureString SecureSecretValue { get; set; } + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "Bulk", ValueFromPipeline = true)] + public Hashtable[] Secrets { get; set; } + [Parameter] public string SecretComment { get; set; } [Parameter] public string ProjectId { get; set; } [Parameter] public string Environment { get; set; } @@ -33,35 +39,62 @@ namespace PSInfisicalAPI.Cmdlets { try { - if (!ShouldProcess(SecretName, "Create Infisical secret")) + InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); + string resolvedProjectId = ResolveProjectId(connection, ProjectId); + string resolvedEnvironment = ResolveEnvironment(connection, Environment); + string resolvedSecretPath = ResolveSecretPath(connection, SecretPath); + string resolvedApiVersion = ResolveApiVersion(connection, ApiVersion); + + if (string.Equals(ParameterSetName, "Bulk", StringComparison.Ordinal)) { + if (Secrets == null || Secrets.Length == 0) { return; } + string target = string.Concat(Secrets.Length, " secret(s)"); + if (!ShouldProcess(target, "Bulk-create Infisical secrets")) { return; } + + InfisicalBulkCreateSecretsRequest bulk = new InfisicalBulkCreateSecretsRequest + { + ProjectId = resolvedProjectId, + Environment = resolvedEnvironment, + SecretPath = resolvedSecretPath, + ApiVersion = resolvedApiVersion, + Secrets = InfisicalBulkSecretConverter.ToCreateItems(Secrets) + }; + + InfisicalSecretsClient bulkClient = new InfisicalSecretsClient(HttpClient, Logger); + InfisicalSecret[] created = bulkClient.CreateBatch(connection, bulk); + if (created != null) + { + foreach (InfisicalSecret secret in created) { WriteObject(secret); } + } + return; } + 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, + ProjectId = resolvedProjectId, + Environment = resolvedEnvironment, + SecretPath = resolvedSecretPath, Type = Type.ToString(), - ApiVersion = ApiVersion, + ApiVersion = resolvedApiVersion, SkipMultilineEncoding = SkipMultilineEncoding.IsPresent ? (bool?)true : null, TagIds = TagIds }; InfisicalSecretsClient client = new InfisicalSecretsClient(HttpClient, Logger); - InfisicalSecret secret = client.Create(connection, request); - if (secret != null) + InfisicalSecret single = client.Create(connection, request); + if (single != null) { - WriteObject(secret); + WriteObject(single); } } catch (Exception exception) diff --git a/src/PSInfisicalAPI/Cmdlets/NewInfisicalTagCmdlet.cs b/src/PSInfisicalAPI/Cmdlets/NewInfisicalTagCmdlet.cs index 21d99d0..bf26869 100644 --- a/src/PSInfisicalAPI/Cmdlets/NewInfisicalTagCmdlet.cs +++ b/src/PSInfisicalAPI/Cmdlets/NewInfisicalTagCmdlet.cs @@ -25,8 +25,9 @@ namespace PSInfisicalAPI.Cmdlets } InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); + string resolvedProjectId = ResolveProjectId(connection, ProjectId); InfisicalTagClient client = new InfisicalTagClient(HttpClient, Logger); - InfisicalTag tag = client.Create(connection, ProjectId, Slug, Name, Color); + InfisicalTag tag = client.Create(connection, resolvedProjectId, Slug, Name, Color); if (tag != null) { WriteObject(tag); diff --git a/src/PSInfisicalAPI/Cmdlets/RemoveInfisicalEnvironmentCmdlet.cs b/src/PSInfisicalAPI/Cmdlets/RemoveInfisicalEnvironmentCmdlet.cs index 54bd9ff..2716bc4 100644 --- a/src/PSInfisicalAPI/Cmdlets/RemoveInfisicalEnvironmentCmdlet.cs +++ b/src/PSInfisicalAPI/Cmdlets/RemoveInfisicalEnvironmentCmdlet.cs @@ -25,8 +25,9 @@ namespace PSInfisicalAPI.Cmdlets } InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); + string resolvedProjectId = ResolveProjectId(connection, ProjectId); InfisicalEnvironmentClient client = new InfisicalEnvironmentClient(HttpClient, Logger); - client.Delete(connection, ProjectId, EnvironmentId); + client.Delete(connection, resolvedProjectId, EnvironmentId); if (PassThru.IsPresent) { diff --git a/src/PSInfisicalAPI/Cmdlets/RemoveInfisicalFolderCmdlet.cs b/src/PSInfisicalAPI/Cmdlets/RemoveInfisicalFolderCmdlet.cs index 825bdf3..7dde5d7 100644 --- a/src/PSInfisicalAPI/Cmdlets/RemoveInfisicalFolderCmdlet.cs +++ b/src/PSInfisicalAPI/Cmdlets/RemoveInfisicalFolderCmdlet.cs @@ -27,8 +27,11 @@ namespace PSInfisicalAPI.Cmdlets } InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); + string resolvedProjectId = ResolveProjectId(connection, ProjectId); + string resolvedEnvironment = ResolveEnvironment(connection, Environment); + string resolvedPath = ResolveSecretPath(connection, Path); InfisicalFolderClient client = new InfisicalFolderClient(HttpClient, Logger); - client.Delete(connection, ProjectId, Environment, FolderId, Path); + client.Delete(connection, resolvedProjectId, resolvedEnvironment, FolderId, resolvedPath); if (PassThru.IsPresent) { diff --git a/src/PSInfisicalAPI/Cmdlets/RemoveInfisicalProjectCmdlet.cs b/src/PSInfisicalAPI/Cmdlets/RemoveInfisicalProjectCmdlet.cs index 6cd89e1..fbab178 100644 --- a/src/PSInfisicalAPI/Cmdlets/RemoveInfisicalProjectCmdlet.cs +++ b/src/PSInfisicalAPI/Cmdlets/RemoveInfisicalProjectCmdlet.cs @@ -8,7 +8,7 @@ namespace PSInfisicalAPI.Cmdlets [Cmdlet(VerbsCommon.Remove, "InfisicalProject", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)] public sealed class RemoveInfisicalProjectCmdlet : InfisicalCmdletBase { - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0)] + [Parameter(ValueFromPipelineByPropertyName = true, Position = 0)] [Alias("Id")] public string ProjectId { get; set; } @@ -18,18 +18,20 @@ namespace PSInfisicalAPI.Cmdlets { try { - if (!ShouldProcess(ProjectId, "Remove Infisical project")) + InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); + string resolvedProjectId = ResolveProjectId(connection, ProjectId); + + if (!ShouldProcess(resolvedProjectId, "Remove Infisical project")) { return; } - InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); InfisicalProjectClient client = new InfisicalProjectClient(HttpClient, Logger); - client.Delete(connection, ProjectId); + client.Delete(connection, resolvedProjectId); if (PassThru.IsPresent) { - WriteObject(ProjectId); + WriteObject(resolvedProjectId); } } catch (Exception exception) diff --git a/src/PSInfisicalAPI/Cmdlets/RemoveInfisicalSecretCmdlet.cs b/src/PSInfisicalAPI/Cmdlets/RemoveInfisicalSecretCmdlet.cs index 3087596..d2154c7 100644 --- a/src/PSInfisicalAPI/Cmdlets/RemoveInfisicalSecretCmdlet.cs +++ b/src/PSInfisicalAPI/Cmdlets/RemoveInfisicalSecretCmdlet.cs @@ -6,12 +6,16 @@ using PSInfisicalAPI.Secrets; namespace PSInfisicalAPI.Cmdlets { - [Cmdlet(VerbsCommon.Remove, "InfisicalSecret", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)] + [Cmdlet(VerbsCommon.Remove, "InfisicalSecret", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High, DefaultParameterSetName = "Single")] public sealed class RemoveInfisicalSecretCmdlet : InfisicalCmdletBase { - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0)] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0, ParameterSetName = "Single")] public string SecretName { get; set; } + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "Bulk", ValueFromPipeline = true)] + [Alias("Names", "SecretKeys")] + public string[] SecretNames { get; set; } + [Parameter] public string ProjectId { get; set; } [Parameter] public string Environment { get; set; } [Parameter] public string SecretPath { get; set; } @@ -23,23 +27,51 @@ namespace PSInfisicalAPI.Cmdlets { try { - if (!ShouldProcess(SecretName, "Remove Infisical secret")) + InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); + string resolvedProjectId = ResolveProjectId(connection, ProjectId); + string resolvedEnvironment = ResolveEnvironment(connection, Environment); + string resolvedSecretPath = ResolveSecretPath(connection, SecretPath); + string resolvedApiVersion = ResolveApiVersion(connection, ApiVersion); + + InfisicalSecretsClient client = new InfisicalSecretsClient(HttpClient, Logger); + + if (string.Equals(ParameterSetName, "Bulk", StringComparison.Ordinal)) { + if (SecretNames == null || SecretNames.Length == 0) { return; } + string target = string.Concat(SecretNames.Length, " secret(s)"); + if (!ShouldProcess(target, "Bulk-remove Infisical secrets")) { return; } + + InfisicalBulkDeleteSecretsRequest bulk = new InfisicalBulkDeleteSecretsRequest + { + ProjectId = resolvedProjectId, + Environment = resolvedEnvironment, + SecretPath = resolvedSecretPath, + ApiVersion = resolvedApiVersion, + SecretNames = SecretNames + }; + + client.DeleteBatch(connection, bulk); + + if (PassThru.IsPresent) + { + foreach (string name in SecretNames) { WriteObject(name); } + } + return; } - InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); + if (!ShouldProcess(SecretName, "Remove Infisical secret")) { return; } + InfisicalDeleteSecretRequest request = new InfisicalDeleteSecretRequest { SecretName = SecretName, - ProjectId = ProjectId, - Environment = Environment, - SecretPath = SecretPath, + ProjectId = resolvedProjectId, + Environment = resolvedEnvironment, + SecretPath = resolvedSecretPath, Type = Type.ToString(), - ApiVersion = ApiVersion + ApiVersion = resolvedApiVersion }; - InfisicalSecretsClient client = new InfisicalSecretsClient(HttpClient, Logger); client.Delete(connection, request); if (PassThru.IsPresent) diff --git a/src/PSInfisicalAPI/Cmdlets/RemoveInfisicalTagCmdlet.cs b/src/PSInfisicalAPI/Cmdlets/RemoveInfisicalTagCmdlet.cs index bb14432..96b3b7e 100644 --- a/src/PSInfisicalAPI/Cmdlets/RemoveInfisicalTagCmdlet.cs +++ b/src/PSInfisicalAPI/Cmdlets/RemoveInfisicalTagCmdlet.cs @@ -25,8 +25,9 @@ namespace PSInfisicalAPI.Cmdlets } InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); + string resolvedProjectId = ResolveProjectId(connection, ProjectId); InfisicalTagClient client = new InfisicalTagClient(HttpClient, Logger); - client.Delete(connection, ProjectId, TagId); + client.Delete(connection, resolvedProjectId, TagId); if (PassThru.IsPresent) { diff --git a/src/PSInfisicalAPI/Cmdlets/UpdateInfisicalEnvironmentCmdlet.cs b/src/PSInfisicalAPI/Cmdlets/UpdateInfisicalEnvironmentCmdlet.cs index ade169e..76de675 100644 --- a/src/PSInfisicalAPI/Cmdlets/UpdateInfisicalEnvironmentCmdlet.cs +++ b/src/PSInfisicalAPI/Cmdlets/UpdateInfisicalEnvironmentCmdlet.cs @@ -29,8 +29,9 @@ namespace PSInfisicalAPI.Cmdlets } InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); + string resolvedProjectId = ResolveProjectId(connection, ProjectId); InfisicalEnvironmentClient client = new InfisicalEnvironmentClient(HttpClient, Logger); - InfisicalEnvironment env = client.Update(connection, ProjectId, EnvironmentId, Name, Slug, Position); + InfisicalEnvironment env = client.Update(connection, resolvedProjectId, EnvironmentId, Name, Slug, Position); if (env != null) { WriteObject(env); diff --git a/src/PSInfisicalAPI/Cmdlets/UpdateInfisicalFolderCmdlet.cs b/src/PSInfisicalAPI/Cmdlets/UpdateInfisicalFolderCmdlet.cs index 891b398..bb5fe36 100644 --- a/src/PSInfisicalAPI/Cmdlets/UpdateInfisicalFolderCmdlet.cs +++ b/src/PSInfisicalAPI/Cmdlets/UpdateInfisicalFolderCmdlet.cs @@ -29,8 +29,11 @@ namespace PSInfisicalAPI.Cmdlets } InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); + string resolvedProjectId = ResolveProjectId(connection, ProjectId); + string resolvedEnvironment = ResolveEnvironment(connection, Environment); + string resolvedPath = ResolveSecretPath(connection, Path); InfisicalFolderClient client = new InfisicalFolderClient(HttpClient, Logger); - InfisicalFolder folder = client.Update(connection, ProjectId, Environment, FolderId, Name, Path); + InfisicalFolder folder = client.Update(connection, resolvedProjectId, resolvedEnvironment, FolderId, Name, resolvedPath); if (folder != null) { WriteObject(folder); diff --git a/src/PSInfisicalAPI/Cmdlets/UpdateInfisicalProjectCmdlet.cs b/src/PSInfisicalAPI/Cmdlets/UpdateInfisicalProjectCmdlet.cs index 3a58b3f..a76cb6b 100644 --- a/src/PSInfisicalAPI/Cmdlets/UpdateInfisicalProjectCmdlet.cs +++ b/src/PSInfisicalAPI/Cmdlets/UpdateInfisicalProjectCmdlet.cs @@ -10,7 +10,7 @@ namespace PSInfisicalAPI.Cmdlets [OutputType(typeof(InfisicalProject))] public sealed class UpdateInfisicalProjectCmdlet : InfisicalCmdletBase { - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0)] + [Parameter(ValueFromPipelineByPropertyName = true, Position = 0)] [Alias("Id")] public string ProjectId { get; set; } @@ -22,14 +22,16 @@ namespace PSInfisicalAPI.Cmdlets { try { - if (!ShouldProcess(ProjectId, "Update Infisical project")) + InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); + string resolvedProjectId = ResolveProjectId(connection, ProjectId); + + if (!ShouldProcess(resolvedProjectId, "Update Infisical project")) { return; } - InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); InfisicalProjectClient client = new InfisicalProjectClient(HttpClient, Logger); - InfisicalProject project = client.Update(connection, ProjectId, Name, Description, AutoCapitalization); + InfisicalProject project = client.Update(connection, resolvedProjectId, Name, Description, AutoCapitalization); if (project != null) { WriteObject(project); diff --git a/src/PSInfisicalAPI/Cmdlets/UpdateInfisicalSecretCmdlet.cs b/src/PSInfisicalAPI/Cmdlets/UpdateInfisicalSecretCmdlet.cs index 359025f..0bb6912 100644 --- a/src/PSInfisicalAPI/Cmdlets/UpdateInfisicalSecretCmdlet.cs +++ b/src/PSInfisicalAPI/Cmdlets/UpdateInfisicalSecretCmdlet.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Management.Automation; using System.Security; using PSInfisicalAPI.Connections; @@ -12,12 +13,16 @@ namespace PSInfisicalAPI.Cmdlets [OutputType(typeof(InfisicalSecret))] public sealed class UpdateInfisicalSecretCmdlet : InfisicalCmdletBase { - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0)] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0, ParameterSetName = "PlainText")] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0, ParameterSetName = "SecureString")] public string SecretName { get; set; } [Parameter(ParameterSetName = "PlainText")] public string SecretValue { get; set; } [Parameter(ParameterSetName = "SecureString")] public SecureString SecureSecretValue { get; set; } + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "Bulk", ValueFromPipeline = true)] + public Hashtable[] Secrets { get; set; } + [Parameter] public string NewSecretName { get; set; } [Parameter] public string SecretComment { get; set; } [Parameter] public string ProjectId { get; set; } @@ -32,36 +37,63 @@ namespace PSInfisicalAPI.Cmdlets { try { - if (!ShouldProcess(SecretName, "Update Infisical secret")) + InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); + string resolvedProjectId = ResolveProjectId(connection, ProjectId); + string resolvedEnvironment = ResolveEnvironment(connection, Environment); + string resolvedSecretPath = ResolveSecretPath(connection, SecretPath); + string resolvedApiVersion = ResolveApiVersion(connection, ApiVersion); + + if (string.Equals(ParameterSetName, "Bulk", StringComparison.Ordinal)) { + if (Secrets == null || Secrets.Length == 0) { return; } + string target = string.Concat(Secrets.Length, " secret(s)"); + if (!ShouldProcess(target, "Bulk-update Infisical secrets")) { return; } + + InfisicalBulkUpdateSecretsRequest bulk = new InfisicalBulkUpdateSecretsRequest + { + ProjectId = resolvedProjectId, + Environment = resolvedEnvironment, + SecretPath = resolvedSecretPath, + ApiVersion = resolvedApiVersion, + Secrets = InfisicalBulkSecretConverter.ToUpdateItems(Secrets) + }; + + InfisicalSecretsClient bulkClient = new InfisicalSecretsClient(HttpClient, Logger); + InfisicalSecret[] updated = bulkClient.UpdateBatch(connection, bulk); + if (updated != null) + { + foreach (InfisicalSecret secret in updated) { WriteObject(secret); } + } + return; } + 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, + ProjectId = resolvedProjectId, + Environment = resolvedEnvironment, + SecretPath = resolvedSecretPath, Type = Type.ToString(), - ApiVersion = ApiVersion, + ApiVersion = resolvedApiVersion, SkipMultilineEncoding = SkipMultilineEncoding.IsPresent ? (bool?)true : null, TagIds = TagIds }; InfisicalSecretsClient client = new InfisicalSecretsClient(HttpClient, Logger); - InfisicalSecret secret = client.Update(connection, request); - if (secret != null) + InfisicalSecret single = client.Update(connection, request); + if (single != null) { - WriteObject(secret); + WriteObject(single); } } catch (Exception exception) diff --git a/src/PSInfisicalAPI/Cmdlets/UpdateInfisicalTagCmdlet.cs b/src/PSInfisicalAPI/Cmdlets/UpdateInfisicalTagCmdlet.cs index 9a6c45b..15aefe6 100644 --- a/src/PSInfisicalAPI/Cmdlets/UpdateInfisicalTagCmdlet.cs +++ b/src/PSInfisicalAPI/Cmdlets/UpdateInfisicalTagCmdlet.cs @@ -29,8 +29,9 @@ namespace PSInfisicalAPI.Cmdlets } InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); + string resolvedProjectId = ResolveProjectId(connection, ProjectId); InfisicalTagClient client = new InfisicalTagClient(HttpClient, Logger); - InfisicalTag tag = client.Update(connection, ProjectId, TagId, Slug, Name, Color); + InfisicalTag tag = client.Update(connection, resolvedProjectId, TagId, Slug, Name, Color); if (tag != null) { WriteObject(tag); diff --git a/src/PSInfisicalAPI/Endpoints/InfisicalEndpointNames.cs b/src/PSInfisicalAPI/Endpoints/InfisicalEndpointNames.cs index 83a8d0e..161c3a5 100644 --- a/src/PSInfisicalAPI/Endpoints/InfisicalEndpointNames.cs +++ b/src/PSInfisicalAPI/Endpoints/InfisicalEndpointNames.cs @@ -15,6 +15,10 @@ namespace PSInfisicalAPI.Endpoints public const string CreateSecret = "CreateSecret"; public const string UpdateSecret = "UpdateSecret"; public const string DeleteSecret = "DeleteSecret"; + public const string BulkCreateSecret = "BulkCreateSecret"; + public const string BulkUpdateSecret = "BulkUpdateSecret"; + public const string BulkDeleteSecret = "BulkDeleteSecret"; + public const string DuplicateSecret = "DuplicateSecret"; public const string ListProjects = "ListProjects"; public const string RetrieveProject = "RetrieveProject"; diff --git a/src/PSInfisicalAPI/Endpoints/InfisicalEndpointRegistry.cs b/src/PSInfisicalAPI/Endpoints/InfisicalEndpointRegistry.cs index 0478a5c..332de5a 100644 --- a/src/PSInfisicalAPI/Endpoints/InfisicalEndpointRegistry.cs +++ b/src/PSInfisicalAPI/Endpoints/InfisicalEndpointRegistry.cs @@ -197,6 +197,54 @@ namespace PSInfisicalAPI.Endpoints RequiresAuthorization = true, ContainsSecretMaterialInResponse = true }); + + Add(map, new InfisicalEndpointDefinition + { + Name = InfisicalEndpointNames.BulkCreateSecret, + Resource = "Secrets", + Version = "v3", + Method = "POST", + Template = "/api/v3/secrets/batch/raw", + RequiresAuthorization = true, + ContainsSecretMaterialInRequest = true, + ContainsSecretMaterialInResponse = true + }); + + Add(map, new InfisicalEndpointDefinition + { + Name = InfisicalEndpointNames.BulkUpdateSecret, + Resource = "Secrets", + Version = "v3", + Method = "PATCH", + Template = "/api/v3/secrets/batch/raw", + RequiresAuthorization = true, + ContainsSecretMaterialInRequest = true, + ContainsSecretMaterialInResponse = true + }); + + Add(map, new InfisicalEndpointDefinition + { + Name = InfisicalEndpointNames.BulkDeleteSecret, + Resource = "Secrets", + Version = "v3", + Method = "DELETE", + Template = "/api/v3/secrets/batch/raw", + RequiresAuthorization = true, + ContainsSecretMaterialInRequest = true, + ContainsSecretMaterialInResponse = true + }); + + Add(map, new InfisicalEndpointDefinition + { + Name = InfisicalEndpointNames.DuplicateSecret, + Resource = "Secrets", + Version = "v4", + Method = "POST", + Template = "/api/v4/secrets/duplicate", + RequiresAuthorization = true, + ContainsSecretMaterialInRequest = true, + ContainsSecretMaterialInResponse = true + }); } private static void RegisterProjects(Dictionary> map) diff --git a/src/PSInfisicalAPI/Secrets/InfisicalBulkSecretConverter.cs b/src/PSInfisicalAPI/Secrets/InfisicalBulkSecretConverter.cs new file mode 100644 index 0000000..ba4f37d --- /dev/null +++ b/src/PSInfisicalAPI/Secrets/InfisicalBulkSecretConverter.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using PSInfisicalAPI.Errors; +using PSInfisicalAPI.Security; + +namespace PSInfisicalAPI.Secrets +{ + public static class InfisicalBulkSecretConverter + { + public static InfisicalBulkCreateSecretItem[] ToCreateItems(IEnumerable input) + { + if (input == null) { return new InfisicalBulkCreateSecretItem[0]; } + + List list = new List(); + foreach (object element in input) + { + Hashtable table = AsHashtable(element); + InfisicalBulkCreateSecretItem item = new InfisicalBulkCreateSecretItem + { + SecretName = GetString(table, "SecretName", "Name", "Key", "SecretKey"), + SecretValue = GetSecretValue(table, "SecretValue", "Value"), + SecretComment = GetString(table, "SecretComment", "Comment"), + SkipMultilineEncoding = GetBool(table, "SkipMultilineEncoding"), + TagIds = GetStringArray(table, "TagIds"), + SecretMetadata = GetStringDictionary(table, "SecretMetadata", "Metadata") + }; + + if (string.IsNullOrEmpty(item.SecretName)) + { + throw new InfisicalConfigurationException("Each bulk-create entry must include 'SecretName' (or 'Name'/'Key')."); + } + + list.Add(item); + } + + return list.ToArray(); + } + + public static InfisicalBulkUpdateSecretItem[] ToUpdateItems(IEnumerable input) + { + if (input == null) { return new InfisicalBulkUpdateSecretItem[0]; } + + List list = new List(); + foreach (object element in input) + { + Hashtable table = AsHashtable(element); + InfisicalBulkUpdateSecretItem item = new InfisicalBulkUpdateSecretItem + { + SecretName = GetString(table, "SecretName", "Name", "Key", "SecretKey"), + NewSecretName = GetString(table, "NewSecretName", "NewName"), + SecretValue = GetSecretValue(table, "SecretValue", "Value"), + SecretComment = GetString(table, "SecretComment", "Comment"), + SkipMultilineEncoding = GetBool(table, "SkipMultilineEncoding"), + TagIds = GetStringArray(table, "TagIds"), + SecretMetadata = GetStringDictionary(table, "SecretMetadata", "Metadata") + }; + + if (string.IsNullOrEmpty(item.SecretName)) + { + throw new InfisicalConfigurationException("Each bulk-update entry must include 'SecretName' (or 'Name'/'Key')."); + } + + list.Add(item); + } + + return list.ToArray(); + } + + private static Hashtable AsHashtable(object element) + { + if (element is Hashtable hashtable) { return hashtable; } + if (element is IDictionary dictionary) + { + Hashtable converted = new Hashtable(StringComparer.OrdinalIgnoreCase); + foreach (DictionaryEntry entry in dictionary) + { + if (entry.Key == null) { continue; } + converted[entry.Key.ToString()] = entry.Value; + } + + return converted; + } + + throw new InfisicalConfigurationException("Bulk secret entries must be Hashtable or IDictionary values."); + } + + private static string GetString(Hashtable table, params string[] keys) + { + foreach (string key in keys) + { + if (table.ContainsKey(key) && table[key] != null) + { + return table[key].ToString(); + } + } + + return null; + } + + private static string GetSecretValue(Hashtable table, params string[] keys) + { + foreach (string key in keys) + { + if (!table.ContainsKey(key)) { continue; } + object value = table[key]; + if (value == null) { return null; } + if (value is System.Security.SecureString secure) + { + return SecureStringUtility.UsePlainText(secure, plain => plain); + } + + return value.ToString(); + } + + return null; + } + + private static bool? GetBool(Hashtable table, string key) + { + if (!table.ContainsKey(key) || table[key] == null) { return null; } + object value = table[key]; + if (value is bool b) { return b; } + bool parsed; + return bool.TryParse(value.ToString(), out parsed) ? parsed : (bool?)null; + } + + private static string[] GetStringArray(Hashtable table, string key) + { + if (!table.ContainsKey(key) || table[key] == null) { return null; } + object value = table[key]; + if (value is string[] direct) { return direct; } + if (value is IEnumerable enumerable && !(value is string)) + { + List items = new List(); + foreach (object item in enumerable) { if (item != null) { items.Add(item.ToString()); } } + return items.ToArray(); + } + + return new[] { value.ToString() }; + } + + private static Dictionary GetStringDictionary(Hashtable table, params string[] keys) + { + foreach (string key in keys) + { + if (!table.ContainsKey(key) || table[key] == null) { continue; } + if (table[key] is IDictionary dictionary) + { + Dictionary result = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (DictionaryEntry entry in dictionary) + { + if (entry.Key == null) { continue; } + result[entry.Key.ToString()] = entry.Value != null ? entry.Value.ToString() : null; + } + + return result; + } + } + + return null; + } + } +} diff --git a/src/PSInfisicalAPI/Secrets/InfisicalSecretDtos.cs b/src/PSInfisicalAPI/Secrets/InfisicalSecretDtos.cs index 8d4ea3a..d4dd9cc 100644 --- a/src/PSInfisicalAPI/Secrets/InfisicalSecretDtos.cs +++ b/src/PSInfisicalAPI/Secrets/InfisicalSecretDtos.cs @@ -88,4 +88,75 @@ namespace PSInfisicalAPI.Secrets [JsonProperty("secretPath", NullValueHandling = NullValueHandling.Ignore)] public string SecretPath { get; set; } [JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)] public string Type { get; set; } } + + internal sealed class InfisicalSecretBatchCreateItemDto + { + [JsonProperty("secretKey")] public string SecretKey { 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; } + [JsonProperty("secretMetadata", NullValueHandling = NullValueHandling.Ignore)] public List SecretMetadata { get; set; } + } + + internal sealed class InfisicalSecretBatchUpdateItemDto + { + [JsonProperty("secretKey")] public string SecretKey { get; set; } + [JsonProperty("newSecretName", NullValueHandling = NullValueHandling.Ignore)] public string NewSecretName { get; set; } + [JsonProperty("secretValue", NullValueHandling = NullValueHandling.Ignore)] 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; } + [JsonProperty("secretMetadata", NullValueHandling = NullValueHandling.Ignore)] public List SecretMetadata { get; set; } + } + + internal sealed class InfisicalSecretBatchDeleteItemDto + { + [JsonProperty("secretKey")] public string SecretKey { get; set; } + } + + internal sealed class InfisicalSecretBatchCreateRequestDto + { + [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("secrets")] public List Secrets { get; set; } + } + + internal sealed class InfisicalSecretBatchUpdateRequestDto + { + [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("mode", NullValueHandling = NullValueHandling.Ignore)] public string Mode { get; set; } + [JsonProperty("secrets")] public List Secrets { get; set; } + } + + internal sealed class InfisicalSecretBatchDeleteRequestDto + { + [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("secrets")] public List Secrets { get; set; } + } + + internal sealed class InfisicalSecretDuplicateAttributesDto + { + [JsonProperty("secretValue", NullValueHandling = NullValueHandling.Ignore)] public bool? SecretValue { get; set; } + [JsonProperty("secretComment", NullValueHandling = NullValueHandling.Ignore)] public bool? SecretComment { get; set; } + [JsonProperty("tags", NullValueHandling = NullValueHandling.Ignore)] public bool? Tags { get; set; } + [JsonProperty("metadata", NullValueHandling = NullValueHandling.Ignore)] public bool? Metadata { get; set; } + } + + internal sealed class InfisicalSecretDuplicateRequestDto + { + [JsonProperty("projectId")] public string ProjectId { get; set; } + [JsonProperty("sourceEnvironment")] public string SourceEnvironment { get; set; } + [JsonProperty("destinationEnvironment")] public string DestinationEnvironment { get; set; } + [JsonProperty("sourceSecretPath", NullValueHandling = NullValueHandling.Ignore)] public string SourceSecretPath { get; set; } + [JsonProperty("destinationSecretPath", NullValueHandling = NullValueHandling.Ignore)] public string DestinationSecretPath { get; set; } + [JsonProperty("secretIds")] public string[] SecretIds { get; set; } + [JsonProperty("overwriteExisting", NullValueHandling = NullValueHandling.Ignore)] public bool? OverwriteExisting { get; set; } + [JsonProperty("attributesToCopy", NullValueHandling = NullValueHandling.Ignore)] public InfisicalSecretDuplicateAttributesDto AttributesToCopy { get; set; } + } } diff --git a/src/PSInfisicalAPI/Secrets/InfisicalSecretQuery.cs b/src/PSInfisicalAPI/Secrets/InfisicalSecretQuery.cs index a5e20d1..0a274d5 100644 --- a/src/PSInfisicalAPI/Secrets/InfisicalSecretQuery.cs +++ b/src/PSInfisicalAPI/Secrets/InfisicalSecretQuery.cs @@ -69,4 +69,69 @@ namespace PSInfisicalAPI.Secrets public string Type { get; set; } public string ApiVersion { get; set; } } + + public sealed class InfisicalBulkCreateSecretItem + { + public string SecretName { get; set; } + public string SecretValue { get; set; } + public string SecretComment { get; set; } + public bool? SkipMultilineEncoding { get; set; } + public string[] TagIds { get; set; } + public Dictionary SecretMetadata { get; set; } + } + + public sealed class InfisicalBulkUpdateSecretItem + { + public string SecretName { get; set; } + public string NewSecretName { get; set; } + public string SecretValue { get; set; } + public string SecretComment { get; set; } + public bool? SkipMultilineEncoding { get; set; } + public string[] TagIds { get; set; } + public Dictionary SecretMetadata { get; set; } + } + + public sealed class InfisicalBulkCreateSecretsRequest + { + public string ProjectId { get; set; } + public string Environment { get; set; } + public string SecretPath { get; set; } + public string ApiVersion { get; set; } + public InfisicalBulkCreateSecretItem[] Secrets { get; set; } + } + + public sealed class InfisicalBulkUpdateSecretsRequest + { + public string ProjectId { get; set; } + public string Environment { get; set; } + public string SecretPath { get; set; } + public string ApiVersion { get; set; } + public string Mode { get; set; } + public InfisicalBulkUpdateSecretItem[] Secrets { get; set; } + } + + public sealed class InfisicalBulkDeleteSecretsRequest + { + public string ProjectId { get; set; } + public string Environment { get; set; } + public string SecretPath { get; set; } + public string ApiVersion { get; set; } + public string[] SecretNames { get; set; } + } + + public sealed class InfisicalDuplicateSecretsRequest + { + public string ProjectId { get; set; } + public string SourceEnvironment { get; set; } + public string DestinationEnvironment { get; set; } + public string SourceSecretPath { get; set; } + public string DestinationSecretPath { get; set; } + public string[] SecretIds { get; set; } + public bool? OverwriteExisting { get; set; } + public bool? CopySecretValue { get; set; } + public bool? CopySecretComment { get; set; } + public bool? CopyTags { get; set; } + public bool? CopyMetadata { get; set; } + public string ApiVersion { get; set; } + } } diff --git a/src/PSInfisicalAPI/Secrets/InfisicalSecretsClient.cs b/src/PSInfisicalAPI/Secrets/InfisicalSecretsClient.cs index 2f62246..9d7e93a 100644 --- a/src/PSInfisicalAPI/Secrets/InfisicalSecretsClient.cs +++ b/src/PSInfisicalAPI/Secrets/InfisicalSecretsClient.cs @@ -224,6 +224,227 @@ namespace PSInfisicalAPI.Secrets } } + public InfisicalSecret[] CreateBatch(InfisicalConnection connection, InfisicalBulkCreateSecretsRequest request) + { + if (connection == null) { throw new ArgumentNullException(nameof(connection)); } + if (request == null) { throw new ArgumentNullException(nameof(request)); } + if (request.Secrets == null || request.Secrets.Length == 0) { throw new InfisicalConfigurationException("At least one secret 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."); } + + List items = new List(request.Secrets.Length); + foreach (InfisicalBulkCreateSecretItem item in request.Secrets) + { + if (item == null) { continue; } + if (string.IsNullOrEmpty(item.SecretName)) { throw new InfisicalConfigurationException("Each bulk-create item requires SecretName."); } + items.Add(new InfisicalSecretBatchCreateItemDto + { + SecretKey = item.SecretName, + SecretValue = item.SecretValue ?? string.Empty, + SecretComment = item.SecretComment, + SkipMultilineEncoding = item.SkipMultilineEncoding, + TagIds = item.TagIds, + SecretMetadata = ToMetadataDtoList(item.SecretMetadata) + }); + } + + InfisicalSecretBatchCreateRequestDto dtoRequest = new InfisicalSecretBatchCreateRequestDto + { + WorkspaceId = resolvedProjectId, + Environment = resolvedEnvironment, + SecretPath = FirstNonEmpty(request.SecretPath, connection.DefaultSecretPath, "/"), + Secrets = items + }; + string body = _serializer.Serialize(dtoRequest); + + try + { + _logger.Information(Component, string.Concat("Attempting to bulk-create ", items.Count.ToString(CultureInfo.InvariantCulture), " Infisical secret(s). Please Wait...")); + InfisicalHttpResponse response = SendWithVersionFallback(connection, InfisicalEndpointNames.BulkCreateSecret, request.ApiVersion, "BulkCreateSecrets", null, null, body); + InfisicalSecretListResponseDto dto = _serializer.Deserialize(response.Body); + response.Clear(); + + InfisicalSecret[] mapped = InfisicalSecretMapper.MapMany(dto != null ? dto.Secrets : null); + _logger.Information(Component, "Infisical bulk secret creation was successful."); + return mapped; + } + catch (Exception) + { + _logger.Error(Component, "Infisical bulk secret creation failed."); + throw; + } + } + + public InfisicalSecret[] UpdateBatch(InfisicalConnection connection, InfisicalBulkUpdateSecretsRequest request) + { + if (connection == null) { throw new ArgumentNullException(nameof(connection)); } + if (request == null) { throw new ArgumentNullException(nameof(request)); } + if (request.Secrets == null || request.Secrets.Length == 0) { throw new InfisicalConfigurationException("At least one secret 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."); } + + List items = new List(request.Secrets.Length); + foreach (InfisicalBulkUpdateSecretItem item in request.Secrets) + { + if (item == null) { continue; } + if (string.IsNullOrEmpty(item.SecretName)) { throw new InfisicalConfigurationException("Each bulk-update item requires SecretName."); } + items.Add(new InfisicalSecretBatchUpdateItemDto + { + SecretKey = item.SecretName, + NewSecretName = item.NewSecretName, + SecretValue = item.SecretValue, + SecretComment = item.SecretComment, + SkipMultilineEncoding = item.SkipMultilineEncoding, + TagIds = item.TagIds, + SecretMetadata = ToMetadataDtoList(item.SecretMetadata) + }); + } + + InfisicalSecretBatchUpdateRequestDto dtoRequest = new InfisicalSecretBatchUpdateRequestDto + { + WorkspaceId = resolvedProjectId, + Environment = resolvedEnvironment, + SecretPath = FirstNonEmpty(request.SecretPath, connection.DefaultSecretPath, "/"), + Mode = request.Mode, + Secrets = items + }; + string body = _serializer.Serialize(dtoRequest); + + try + { + _logger.Information(Component, string.Concat("Attempting to bulk-update ", items.Count.ToString(CultureInfo.InvariantCulture), " Infisical secret(s). Please Wait...")); + InfisicalHttpResponse response = SendWithVersionFallback(connection, InfisicalEndpointNames.BulkUpdateSecret, request.ApiVersion, "BulkUpdateSecrets", null, null, body); + InfisicalSecretListResponseDto dto = _serializer.Deserialize(response.Body); + response.Clear(); + + InfisicalSecret[] mapped = InfisicalSecretMapper.MapMany(dto != null ? dto.Secrets : null); + _logger.Information(Component, "Infisical bulk secret update was successful."); + return mapped; + } + catch (Exception) + { + _logger.Error(Component, "Infisical bulk secret update failed."); + throw; + } + } + + public void DeleteBatch(InfisicalConnection connection, InfisicalBulkDeleteSecretsRequest request) + { + if (connection == null) { throw new ArgumentNullException(nameof(connection)); } + if (request == null) { throw new ArgumentNullException(nameof(request)); } + if (request.SecretNames == null || request.SecretNames.Length == 0) { throw new InfisicalConfigurationException("At least one secret name 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."); } + + List items = new List(request.SecretNames.Length); + foreach (string name in request.SecretNames) + { + if (string.IsNullOrEmpty(name)) { continue; } + items.Add(new InfisicalSecretBatchDeleteItemDto { SecretKey = name }); + } + + InfisicalSecretBatchDeleteRequestDto dtoRequest = new InfisicalSecretBatchDeleteRequestDto + { + WorkspaceId = resolvedProjectId, + Environment = resolvedEnvironment, + SecretPath = FirstNonEmpty(request.SecretPath, connection.DefaultSecretPath, "/"), + Secrets = items + }; + string body = _serializer.Serialize(dtoRequest); + + try + { + _logger.Information(Component, string.Concat("Attempting to bulk-delete ", items.Count.ToString(CultureInfo.InvariantCulture), " Infisical secret(s). Please Wait...")); + InfisicalHttpResponse response = SendWithVersionFallback(connection, InfisicalEndpointNames.BulkDeleteSecret, request.ApiVersion, "BulkDeleteSecrets", null, null, body); + response.Clear(); + _logger.Information(Component, "Infisical bulk secret deletion was successful."); + } + catch (Exception) + { + _logger.Error(Component, "Infisical bulk secret deletion failed."); + throw; + } + } + + public InfisicalSecret[] Duplicate(InfisicalConnection connection, InfisicalDuplicateSecretsRequest request) + { + if (connection == null) { throw new ArgumentNullException(nameof(connection)); } + if (request == null) { throw new ArgumentNullException(nameof(request)); } + if (request.SecretIds == null || request.SecretIds.Length == 0) { throw new InfisicalConfigurationException("At least one SecretId is required."); } + + string resolvedProjectId = FirstNonEmpty(request.ProjectId, connection.ProjectId); + string resolvedSourceEnv = FirstNonEmpty(request.SourceEnvironment, connection.Environment); + if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); } + if (string.IsNullOrEmpty(resolvedSourceEnv)) { throw new InfisicalConfigurationException("SourceEnvironment is required."); } + if (string.IsNullOrEmpty(request.DestinationEnvironment)) { throw new InfisicalConfigurationException("DestinationEnvironment is required."); } + + string resolvedSourcePath = FirstNonEmpty(request.SourceSecretPath, connection.DefaultSecretPath, "/"); + string resolvedDestPath = FirstNonEmpty(request.DestinationSecretPath, resolvedSourcePath); + + InfisicalSecretDuplicateAttributesDto attributes = null; + if (request.CopySecretValue.HasValue || request.CopySecretComment.HasValue || request.CopyTags.HasValue || request.CopyMetadata.HasValue) + { + attributes = new InfisicalSecretDuplicateAttributesDto + { + SecretValue = request.CopySecretValue, + SecretComment = request.CopySecretComment, + Tags = request.CopyTags, + Metadata = request.CopyMetadata + }; + } + + InfisicalSecretDuplicateRequestDto dtoRequest = new InfisicalSecretDuplicateRequestDto + { + ProjectId = resolvedProjectId, + SourceEnvironment = resolvedSourceEnv, + DestinationEnvironment = request.DestinationEnvironment, + SourceSecretPath = resolvedSourcePath, + DestinationSecretPath = resolvedDestPath, + SecretIds = request.SecretIds, + OverwriteExisting = request.OverwriteExisting, + AttributesToCopy = attributes + }; + string body = _serializer.Serialize(dtoRequest); + + try + { + _logger.Information(Component, string.Concat("Attempting to duplicate ", request.SecretIds.Length.ToString(CultureInfo.InvariantCulture), " Infisical secret(s). Please Wait...")); + InfisicalHttpResponse response = SendWithVersionFallback(connection, InfisicalEndpointNames.DuplicateSecret, request.ApiVersion, "DuplicateSecrets", null, null, body); + InfisicalSecretListResponseDto dto = _serializer.Deserialize(response.Body); + response.Clear(); + + InfisicalSecret[] mapped = InfisicalSecretMapper.MapMany(dto != null ? dto.Secrets : null); + _logger.Information(Component, "Infisical secret duplication was successful."); + return mapped; + } + catch (Exception) + { + _logger.Error(Component, "Infisical secret duplication failed."); + throw; + } + } + + private static List ToMetadataDtoList(Dictionary metadata) + { + if (metadata == null || metadata.Count == 0) { return null; } + List list = new List(metadata.Count); + foreach (KeyValuePair kvp in metadata) + { + list.Add(new InfisicalSecretMetadataDto { Key = kvp.Key, Value = kvp.Value }); + } + + return list; + } + public void Delete(InfisicalConnection connection, InfisicalDeleteSecretRequest request) { if (connection == null) { throw new ArgumentNullException(nameof(connection)); }