diff --git a/CHANGELOG.md b/CHANGELOG.md index 251ad61..c63b5cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,19 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) loos ## Unreleased +## 2026.06.04.0005 + +- Build produced from commit e0a6ef02df3e. + +## Unreleased (carried forward) + +- **Bulk v4 batch routes**: Endpoint registry now registers `POST|PATCH|DELETE /api/v4/secrets/batch` as the preferred candidates for `BulkCreateSecret`/`BulkUpdateSecret`/`BulkDeleteSecret`; the existing v3 raw routes (`/api/v3/secrets/batch/raw`) remain as automatic fallback. Batch request DTOs serialize both `projectId` (required by v4) and `workspaceId` (accepted by v3) when populated. +- **Strongly-typed bulk input**: `-Secrets` on `New-InfisicalSecret` and `Update-InfisicalSecret` is now `IDictionary[]` instead of `Hashtable[]`. `InfisicalBulkSecretConverter` accepts `IEnumerable>` and parses `TagIds` from a comma-separated string. Nested `Metadata`/`SecretMetadata` dictionaries are no longer accepted in the bulk hashtable surface (set `SecretMetadata` programmatically on `InfisicalBulkCreateSecretItem`/`InfisicalBulkUpdateSecretItem` if needed). + ## 2026.06.03.2207 - Build produced from commit 09c3d5c68bbc. -- **M9 — Bulk, Duplicate & Inheritance**: +- **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. diff --git a/Module/PSInfisicalAPI/PSInfisicalAPI.psd1 b/Module/PSInfisicalAPI/PSInfisicalAPI.psd1 index bd0557e..2d0e294 100644 --- a/Module/PSInfisicalAPI/PSInfisicalAPI.psd1 +++ b/Module/PSInfisicalAPI/PSInfisicalAPI.psd1 @@ -1,6 +1,6 @@ @{ RootModule = 'PSInfisicalAPI.psm1' - ModuleVersion = '2026.06.03.2207' + ModuleVersion = '2026.06.04.0005' GUID = 'b8a2f3d4-7c51-4d2f-9e6a-1f0c8b3d4e51' Author = 'Grace Solutions' CompanyName = 'Grace Solutions' @@ -51,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 = '09c3d5c68bbc' + CommitHash = 'e0a6ef02df3e' } } } \ No newline at end of file diff --git a/Module/PSInfisicalAPI/bin/PSInfisicalAPI.dll b/Module/PSInfisicalAPI/bin/PSInfisicalAPI.dll index ca605fc..c2228bd 100644 Binary files a/Module/PSInfisicalAPI/bin/PSInfisicalAPI.dll and b/Module/PSInfisicalAPI/bin/PSInfisicalAPI.dll differ diff --git a/src/PSInfisicalAPI.Tests/BulkSecretConverterTests.cs b/src/PSInfisicalAPI.Tests/BulkSecretConverterTests.cs index 7972862..81f3870 100644 --- a/src/PSInfisicalAPI.Tests/BulkSecretConverterTests.cs +++ b/src/PSInfisicalAPI.Tests/BulkSecretConverterTests.cs @@ -1,4 +1,4 @@ -using System.Collections; +using System.Collections.Generic; using PSInfisicalAPI.Errors; using PSInfisicalAPI.Secrets; using Xunit; @@ -10,16 +10,16 @@ namespace PSInfisicalAPI.Tests [Fact] public void ToCreateItems_Maps_Standard_Keys() { - Hashtable entry = new Hashtable + Dictionary entry = new Dictionary { { "SecretName", "API_KEY" }, { "SecretValue", "abc" }, { "SecretComment", "primary" }, - { "SkipMultilineEncoding", true }, - { "TagIds", new[] { "tag-1", "tag-2" } } + { "SkipMultilineEncoding", "true" }, + { "TagIds", "tag-1,tag-2" } }; - InfisicalBulkCreateSecretItem[] items = InfisicalBulkSecretConverter.ToCreateItems(new[] { entry }); + InfisicalBulkCreateSecretItem[] items = InfisicalBulkSecretConverter.ToCreateItems(new IDictionary[] { entry }); Assert.Single(items); Assert.Equal("API_KEY", items[0].SecretName); Assert.Equal("abc", items[0].SecretValue); @@ -31,13 +31,13 @@ namespace PSInfisicalAPI.Tests [Fact] public void ToCreateItems_Accepts_Name_Alias_For_SecretName() { - Hashtable entry = new Hashtable + Dictionary entry = new Dictionary { { "Name", "API_KEY" }, { "Value", "abc" } }; - InfisicalBulkCreateSecretItem[] items = InfisicalBulkSecretConverter.ToCreateItems(new[] { entry }); + InfisicalBulkCreateSecretItem[] items = InfisicalBulkSecretConverter.ToCreateItems(new IDictionary[] { entry }); Assert.Single(items); Assert.Equal("API_KEY", items[0].SecretName); Assert.Equal("abc", items[0].SecretValue); @@ -46,23 +46,23 @@ namespace PSInfisicalAPI.Tests [Fact] public void ToCreateItems_Without_SecretName_Throws() { - Hashtable entry = new Hashtable { { "Value", "abc" } }; + Dictionary entry = new Dictionary { { "Value", "abc" } }; Assert.Throws(() => - InfisicalBulkSecretConverter.ToCreateItems(new[] { entry })); + InfisicalBulkSecretConverter.ToCreateItems(new IDictionary[] { entry })); } [Fact] public void ToUpdateItems_Maps_NewSecretName() { - Hashtable entry = new Hashtable + Dictionary entry = new Dictionary { { "SecretName", "API_KEY" }, { "NewSecretName", "RENAMED" }, { "SecretValue", "new-value" } }; - InfisicalBulkUpdateSecretItem[] items = InfisicalBulkSecretConverter.ToUpdateItems(new[] { entry }); + InfisicalBulkUpdateSecretItem[] items = InfisicalBulkSecretConverter.ToUpdateItems(new IDictionary[] { entry }); Assert.Single(items); Assert.Equal("API_KEY", items[0].SecretName); Assert.Equal("RENAMED", items[0].NewSecretName); @@ -70,20 +70,17 @@ namespace PSInfisicalAPI.Tests } [Fact] - public void ToCreateItems_Maps_Metadata_Dictionary() + public void ToCreateItems_Trims_TagId_Whitespace_And_Skips_Empty() { - Hashtable meta = new Hashtable { { "owner", "platform" }, { "tier", "1" } }; - Hashtable entry = new Hashtable + Dictionary entry = new Dictionary { { "SecretName", "API_KEY" }, { "SecretValue", "abc" }, - { "Metadata", meta } + { "TagIds", " tag-1 , , tag-2 " } }; - 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"]); + InfisicalBulkCreateSecretItem[] items = InfisicalBulkSecretConverter.ToCreateItems(new IDictionary[] { entry }); + Assert.Equal(new[] { "tag-1", "tag-2" }, items[0].TagIds); } [Fact] diff --git a/src/PSInfisicalAPI.Tests/BulkSecretDtoTests.cs b/src/PSInfisicalAPI.Tests/BulkSecretDtoTests.cs index b4a4474..60af355 100644 --- a/src/PSInfisicalAPI.Tests/BulkSecretDtoTests.cs +++ b/src/PSInfisicalAPI.Tests/BulkSecretDtoTests.cs @@ -59,12 +59,14 @@ namespace PSInfisicalAPI.Tests object dto = MakeDto("PSInfisicalAPI.Secrets.InfisicalSecretBatchCreateRequestDto"); dto.GetType().GetProperty("WorkspaceId").SetValue(dto, "wks-1"); + dto.GetType().GetProperty("ProjectId").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("wks-1", (string)json["projectId"]); Assert.Equal("prod", (string)json["environment"]); Assert.Equal("/db", (string)json["secretPath"]); JArray secrets = (JArray)json["secrets"]; @@ -73,6 +75,31 @@ namespace PSInfisicalAPI.Tests Assert.Equal("V1", (string)secrets[0]["secretValue"]); } + [Fact] + public void BatchCreateRequest_Omits_Null_WorkspaceId_When_Only_ProjectId_Set() + { + object dto = MakeDto("PSInfisicalAPI.Secrets.InfisicalSecretBatchCreateRequestDto"); + dto.GetType().GetProperty("ProjectId").SetValue(dto, "wks-1"); + dto.GetType().GetProperty("Environment").SetValue(dto, "prod"); + + JObject json = JObject.Parse(JsonConvert.SerializeObject(dto)); + Assert.False(json.ContainsKey("workspaceId")); + Assert.Equal("wks-1", (string)json["projectId"]); + } + + [Fact] + public void BatchUpdateRequest_Includes_ProjectId_Alongside_WorkspaceId() + { + object dto = MakeDto("PSInfisicalAPI.Secrets.InfisicalSecretBatchUpdateRequestDto"); + dto.GetType().GetProperty("WorkspaceId").SetValue(dto, "wks-1"); + dto.GetType().GetProperty("ProjectId").SetValue(dto, "wks-1"); + dto.GetType().GetProperty("Environment").SetValue(dto, "prod"); + + JObject json = JObject.Parse(JsonConvert.SerializeObject(dto)); + Assert.Equal("wks-1", (string)json["workspaceId"]); + Assert.Equal("wks-1", (string)json["projectId"]); + } + [Fact] public void BatchDeleteRequest_Serializes_With_Secret_Keys() { @@ -86,11 +113,13 @@ namespace PSInfisicalAPI.Tests object dto = MakeDto("PSInfisicalAPI.Secrets.InfisicalSecretBatchDeleteRequestDto"); dto.GetType().GetProperty("WorkspaceId").SetValue(dto, "wks-1"); + dto.GetType().GetProperty("ProjectId").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"]); + Assert.Equal("wks-1", (string)json["projectId"]); JArray secrets = (JArray)json["secrets"]; Assert.Single(secrets); Assert.Equal("K1", (string)secrets[0]["secretKey"]); diff --git a/src/PSInfisicalAPI.Tests/EndpointRegistryTests.cs b/src/PSInfisicalAPI.Tests/EndpointRegistryTests.cs index d931695..190a3ad 100644 --- a/src/PSInfisicalAPI.Tests/EndpointRegistryTests.cs +++ b/src/PSInfisicalAPI.Tests/EndpointRegistryTests.cs @@ -71,9 +71,9 @@ 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.BulkCreateSecret, "POST", "/api/v4/secrets/batch")] + [InlineData(InfisicalEndpointNames.BulkUpdateSecret, "PATCH", "/api/v4/secrets/batch")] + [InlineData(InfisicalEndpointNames.BulkDeleteSecret, "DELETE", "/api/v4/secrets/batch")] [InlineData(InfisicalEndpointNames.DuplicateSecret, "POST", "/api/v4/secrets/duplicate")] public void Registered_Endpoints_Have_Expected_Shape(string name, string method, string template) { @@ -81,5 +81,19 @@ namespace PSInfisicalAPI.Tests Assert.Equal(method, definition.Method); Assert.Equal(template, definition.Template); } + + [Theory] + [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")] + public void Bulk_Endpoints_Retain_V3_Fallback_Candidate(string name, string method, string template) + { + System.Collections.Generic.IReadOnlyList candidates = InfisicalEndpointRegistry.GetCandidates(name); + Assert.Equal(2, candidates.Count); + Assert.Equal("v4", candidates[0].Version); + Assert.Equal("v3", candidates[1].Version); + Assert.Equal(method, candidates[1].Method); + Assert.Equal(template, candidates[1].Template); + } } } diff --git a/src/PSInfisicalAPI/Cmdlets/NewInfisicalSecretCmdlet.cs b/src/PSInfisicalAPI/Cmdlets/NewInfisicalSecretCmdlet.cs index 2935f2e..f853073 100644 --- a/src/PSInfisicalAPI/Cmdlets/NewInfisicalSecretCmdlet.cs +++ b/src/PSInfisicalAPI/Cmdlets/NewInfisicalSecretCmdlet.cs @@ -1,5 +1,5 @@ using System; -using System.Collections; +using System.Collections.Generic; using System.Management.Automation; using System.Security; using PSInfisicalAPI.Connections; @@ -24,7 +24,7 @@ namespace PSInfisicalAPI.Cmdlets public SecureString SecureSecretValue { get; set; } [Parameter(Mandatory = true, Position = 0, ParameterSetName = "Bulk", ValueFromPipeline = true)] - public Hashtable[] Secrets { get; set; } + public IDictionary[] Secrets { get; set; } [Parameter] public string SecretComment { get; set; } [Parameter] public string ProjectId { get; set; } diff --git a/src/PSInfisicalAPI/Cmdlets/UpdateInfisicalSecretCmdlet.cs b/src/PSInfisicalAPI/Cmdlets/UpdateInfisicalSecretCmdlet.cs index 0bb6912..9ebd1f6 100644 --- a/src/PSInfisicalAPI/Cmdlets/UpdateInfisicalSecretCmdlet.cs +++ b/src/PSInfisicalAPI/Cmdlets/UpdateInfisicalSecretCmdlet.cs @@ -1,5 +1,5 @@ using System; -using System.Collections; +using System.Collections.Generic; using System.Management.Automation; using System.Security; using PSInfisicalAPI.Connections; @@ -21,7 +21,7 @@ namespace PSInfisicalAPI.Cmdlets [Parameter(ParameterSetName = "SecureString")] public SecureString SecureSecretValue { get; set; } [Parameter(Mandatory = true, Position = 0, ParameterSetName = "Bulk", ValueFromPipeline = true)] - public Hashtable[] Secrets { get; set; } + public IDictionary[] Secrets { get; set; } [Parameter] public string NewSecretName { get; set; } [Parameter] public string SecretComment { get; set; } diff --git a/src/PSInfisicalAPI/Endpoints/InfisicalEndpointRegistry.cs b/src/PSInfisicalAPI/Endpoints/InfisicalEndpointRegistry.cs index 332de5a..4aceb25 100644 --- a/src/PSInfisicalAPI/Endpoints/InfisicalEndpointRegistry.cs +++ b/src/PSInfisicalAPI/Endpoints/InfisicalEndpointRegistry.cs @@ -198,6 +198,18 @@ namespace PSInfisicalAPI.Endpoints ContainsSecretMaterialInResponse = true }); + Add(map, new InfisicalEndpointDefinition + { + Name = InfisicalEndpointNames.BulkCreateSecret, + Resource = "Secrets", + Version = "v4", + Method = "POST", + Template = "/api/v4/secrets/batch", + RequiresAuthorization = true, + ContainsSecretMaterialInRequest = true, + ContainsSecretMaterialInResponse = true + }); + Add(map, new InfisicalEndpointDefinition { Name = InfisicalEndpointNames.BulkCreateSecret, @@ -210,6 +222,18 @@ namespace PSInfisicalAPI.Endpoints ContainsSecretMaterialInResponse = true }); + Add(map, new InfisicalEndpointDefinition + { + Name = InfisicalEndpointNames.BulkUpdateSecret, + Resource = "Secrets", + Version = "v4", + Method = "PATCH", + Template = "/api/v4/secrets/batch", + RequiresAuthorization = true, + ContainsSecretMaterialInRequest = true, + ContainsSecretMaterialInResponse = true + }); + Add(map, new InfisicalEndpointDefinition { Name = InfisicalEndpointNames.BulkUpdateSecret, @@ -222,6 +246,18 @@ namespace PSInfisicalAPI.Endpoints ContainsSecretMaterialInResponse = true }); + Add(map, new InfisicalEndpointDefinition + { + Name = InfisicalEndpointNames.BulkDeleteSecret, + Resource = "Secrets", + Version = "v4", + Method = "DELETE", + Template = "/api/v4/secrets/batch", + RequiresAuthorization = true, + ContainsSecretMaterialInRequest = true, + ContainsSecretMaterialInResponse = true + }); + Add(map, new InfisicalEndpointDefinition { Name = InfisicalEndpointNames.BulkDeleteSecret, diff --git a/src/PSInfisicalAPI/Secrets/InfisicalBulkSecretConverter.cs b/src/PSInfisicalAPI/Secrets/InfisicalBulkSecretConverter.cs index ba4f37d..8b7aab5 100644 --- a/src/PSInfisicalAPI/Secrets/InfisicalBulkSecretConverter.cs +++ b/src/PSInfisicalAPI/Secrets/InfisicalBulkSecretConverter.cs @@ -1,29 +1,27 @@ 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) + public static InfisicalBulkCreateSecretItem[] ToCreateItems(IEnumerable> input) { if (input == null) { return new InfisicalBulkCreateSecretItem[0]; } List list = new List(); - foreach (object element in input) + foreach (IDictionary entry in input) { - Hashtable table = AsHashtable(element); + if (entry == null) { continue; } + IDictionary table = Normalize(entry); InfisicalBulkCreateSecretItem item = new InfisicalBulkCreateSecretItem { SecretName = GetString(table, "SecretName", "Name", "Key", "SecretKey"), - SecretValue = GetSecretValue(table, "SecretValue", "Value"), + SecretValue = GetString(table, "SecretValue", "Value"), SecretComment = GetString(table, "SecretComment", "Comment"), SkipMultilineEncoding = GetBool(table, "SkipMultilineEncoding"), - TagIds = GetStringArray(table, "TagIds"), - SecretMetadata = GetStringDictionary(table, "SecretMetadata", "Metadata") + TagIds = GetStringArray(table, "TagIds") }; if (string.IsNullOrEmpty(item.SecretName)) @@ -37,23 +35,23 @@ namespace PSInfisicalAPI.Secrets return list.ToArray(); } - public static InfisicalBulkUpdateSecretItem[] ToUpdateItems(IEnumerable input) + public static InfisicalBulkUpdateSecretItem[] ToUpdateItems(IEnumerable> input) { if (input == null) { return new InfisicalBulkUpdateSecretItem[0]; } List list = new List(); - foreach (object element in input) + foreach (IDictionary entry in input) { - Hashtable table = AsHashtable(element); + if (entry == null) { continue; } + IDictionary table = Normalize(entry); InfisicalBulkUpdateSecretItem item = new InfisicalBulkUpdateSecretItem { SecretName = GetString(table, "SecretName", "Name", "Key", "SecretKey"), NewSecretName = GetString(table, "NewSecretName", "NewName"), - SecretValue = GetSecretValue(table, "SecretValue", "Value"), + SecretValue = GetString(table, "SecretValue", "Value"), SecretComment = GetString(table, "SecretComment", "Comment"), SkipMultilineEncoding = GetBool(table, "SkipMultilineEncoding"), - TagIds = GetStringArray(table, "TagIds"), - SecretMetadata = GetStringDictionary(table, "SecretMetadata", "Metadata") + TagIds = GetStringArray(table, "TagIds") }; if (string.IsNullOrEmpty(item.SecretName)) @@ -67,98 +65,53 @@ namespace PSInfisicalAPI.Secrets return list.ToArray(); } - private static Hashtable AsHashtable(object element) + private static IDictionary Normalize(IDictionary source) { - if (element is Hashtable hashtable) { return hashtable; } - if (element is IDictionary dictionary) + Dictionary normalized = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (KeyValuePair kvp in source) { - Hashtable converted = new Hashtable(StringComparer.OrdinalIgnoreCase); - foreach (DictionaryEntry entry in dictionary) - { - if (entry.Key == null) { continue; } - converted[entry.Key.ToString()] = entry.Value; - } - - return converted; + if (string.IsNullOrEmpty(kvp.Key)) { continue; } + normalized[kvp.Key] = kvp.Value; } - throw new InfisicalConfigurationException("Bulk secret entries must be Hashtable or IDictionary values."); + return normalized; } - private static string GetString(Hashtable table, params string[] keys) + private static string GetString(IDictionary table, params string[] keys) { foreach (string key in keys) { - if (table.ContainsKey(key) && table[key] != null) + string value; + if (table.TryGetValue(key, out value) && !string.IsNullOrEmpty(value)) { - return table[key].ToString(); + return value; } } return null; } - private static string GetSecretValue(Hashtable table, params string[] keys) + private static bool? GetBool(IDictionary table, string key) { - 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; } + string value; + if (!table.TryGetValue(key, out value) || string.IsNullOrEmpty(value)) { return null; } bool parsed; - return bool.TryParse(value.ToString(), out parsed) ? parsed : (bool?)null; + return bool.TryParse(value, out parsed) ? parsed : (bool?)null; } - private static string[] GetStringArray(Hashtable table, string key) + private static string[] GetStringArray(IDictionary 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)) + string value; + if (!table.TryGetValue(key, out value) || string.IsNullOrEmpty(value)) { return null; } + string[] parts = value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + List trimmed = new List(parts.Length); + foreach (string part in parts) { - List items = new List(); - foreach (object item in enumerable) { if (item != null) { items.Add(item.ToString()); } } - return items.ToArray(); + string item = part.Trim(); + if (!string.IsNullOrEmpty(item)) { trimmed.Add(item); } } - 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; + return trimmed.Count == 0 ? null : trimmed.ToArray(); } } } diff --git a/src/PSInfisicalAPI/Secrets/InfisicalSecretDtos.cs b/src/PSInfisicalAPI/Secrets/InfisicalSecretDtos.cs index d4dd9cc..7f4caab 100644 --- a/src/PSInfisicalAPI/Secrets/InfisicalSecretDtos.cs +++ b/src/PSInfisicalAPI/Secrets/InfisicalSecretDtos.cs @@ -117,7 +117,8 @@ namespace PSInfisicalAPI.Secrets internal sealed class InfisicalSecretBatchCreateRequestDto { - [JsonProperty("workspaceId")] public string WorkspaceId { get; set; } + [JsonProperty("workspaceId", NullValueHandling = NullValueHandling.Ignore)] public string WorkspaceId { get; set; } + [JsonProperty("projectId", NullValueHandling = NullValueHandling.Ignore)] public string ProjectId { 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; } @@ -125,7 +126,8 @@ namespace PSInfisicalAPI.Secrets internal sealed class InfisicalSecretBatchUpdateRequestDto { - [JsonProperty("workspaceId")] public string WorkspaceId { get; set; } + [JsonProperty("workspaceId", NullValueHandling = NullValueHandling.Ignore)] public string WorkspaceId { get; set; } + [JsonProperty("projectId", NullValueHandling = NullValueHandling.Ignore)] public string ProjectId { 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; } @@ -134,7 +136,8 @@ namespace PSInfisicalAPI.Secrets internal sealed class InfisicalSecretBatchDeleteRequestDto { - [JsonProperty("workspaceId")] public string WorkspaceId { get; set; } + [JsonProperty("workspaceId", NullValueHandling = NullValueHandling.Ignore)] public string WorkspaceId { get; set; } + [JsonProperty("projectId", NullValueHandling = NullValueHandling.Ignore)] public string ProjectId { 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; } diff --git a/src/PSInfisicalAPI/Secrets/InfisicalSecretsClient.cs b/src/PSInfisicalAPI/Secrets/InfisicalSecretsClient.cs index 9d7e93a..8e4161b 100644 --- a/src/PSInfisicalAPI/Secrets/InfisicalSecretsClient.cs +++ b/src/PSInfisicalAPI/Secrets/InfisicalSecretsClient.cs @@ -254,6 +254,7 @@ namespace PSInfisicalAPI.Secrets InfisicalSecretBatchCreateRequestDto dtoRequest = new InfisicalSecretBatchCreateRequestDto { WorkspaceId = resolvedProjectId, + ProjectId = resolvedProjectId, Environment = resolvedEnvironment, SecretPath = FirstNonEmpty(request.SecretPath, connection.DefaultSecretPath, "/"), Secrets = items @@ -309,6 +310,7 @@ namespace PSInfisicalAPI.Secrets InfisicalSecretBatchUpdateRequestDto dtoRequest = new InfisicalSecretBatchUpdateRequestDto { WorkspaceId = resolvedProjectId, + ProjectId = resolvedProjectId, Environment = resolvedEnvironment, SecretPath = FirstNonEmpty(request.SecretPath, connection.DefaultSecretPath, "/"), Mode = request.Mode, @@ -355,6 +357,7 @@ namespace PSInfisicalAPI.Secrets InfisicalSecretBatchDeleteRequestDto dtoRequest = new InfisicalSecretBatchDeleteRequestDto { WorkspaceId = resolvedProjectId, + ProjectId = resolvedProjectId, Environment = resolvedEnvironment, SecretPath = FirstNonEmpty(request.SecretPath, connection.DefaultSecretPath, "/"), Secrets = items