Tests: roll forward to latest major .NET runtime #3

Merged
gsadmin merged 12 commits from dev into main 2026-06-04 00:47:39 +00:00
40 changed files with 1282 additions and 65 deletions
Showing only changes of commit e0a6ef02df - Show all commits
+19
View File
@@ -6,6 +6,25 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) loos
## Unreleased ## 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 ## 2026.06.03.2136
- Build produced from commit d9822aab7a4a. - Build produced from commit d9822aab7a4a.
+3 -2
View File
@@ -1,6 +1,6 @@
@{ @{
RootModule = 'PSInfisicalAPI.psm1' RootModule = 'PSInfisicalAPI.psm1'
ModuleVersion = '2026.06.03.2136' ModuleVersion = '2026.06.03.2207'
GUID = 'b8a2f3d4-7c51-4d2f-9e6a-1f0c8b3d4e51' GUID = 'b8a2f3d4-7c51-4d2f-9e6a-1f0c8b3d4e51'
Author = 'Grace Solutions' Author = 'Grace Solutions'
CompanyName = 'Grace Solutions' CompanyName = 'Grace Solutions'
@@ -17,6 +17,7 @@
'New-InfisicalSecret', 'New-InfisicalSecret',
'Update-InfisicalSecret', 'Update-InfisicalSecret',
'Remove-InfisicalSecret', 'Remove-InfisicalSecret',
'Copy-InfisicalSecret',
'ConvertTo-InfisicalSecretDictionary', 'ConvertTo-InfisicalSecretDictionary',
'Export-InfisicalSecrets', 'Export-InfisicalSecrets',
'Get-InfisicalProjects', 'Get-InfisicalProjects',
@@ -50,7 +51,7 @@
LicenseUri = 'https://www.gnu.org/licenses/agpl-3.0.html' LicenseUri = 'https://www.gnu.org/licenses/agpl-3.0.html'
ProjectUri = 'https://prod.git.gracesolution.info/gsadmin/PSInfisicalAPI' ProjectUri = 'https://prod.git.gracesolution.info/gsadmin/PSInfisicalAPI'
ReleaseNotes = 'See CHANGELOG.md in the project repository for release history.' ReleaseNotes = 'See CHANGELOG.md in the project repository for release history.'
CommitHash = 'd9822aab7a4a' CommitHash = '09c3d5c68bbc'
} }
} }
} }
Binary file not shown.
+1
View File
@@ -105,6 +105,7 @@ function Write-Manifest {
'New-InfisicalSecret', 'New-InfisicalSecret',
'Update-InfisicalSecret', 'Update-InfisicalSecret',
'Remove-InfisicalSecret', 'Remove-InfisicalSecret',
'Copy-InfisicalSecret',
'ConvertTo-InfisicalSecretDictionary', 'ConvertTo-InfisicalSecretDictionary',
'Export-InfisicalSecrets', 'Export-InfisicalSecrets',
'Get-InfisicalProjects', 'Get-InfisicalProjects',
+2
View File
@@ -16,6 +16,7 @@ Get-InfisicalSecret
New-InfisicalSecret New-InfisicalSecret
Update-InfisicalSecret Update-InfisicalSecret
Remove-InfisicalSecret Remove-InfisicalSecret
Copy-InfisicalSecret
ConvertTo-InfisicalSecretDictionary ConvertTo-InfisicalSecretDictionary
Export-InfisicalSecrets Export-InfisicalSecrets
Get-InfisicalProjects Get-InfisicalProjects
@@ -224,6 +225,7 @@ Example shape:
'New-InfisicalSecret', 'New-InfisicalSecret',
'Update-InfisicalSecret', 'Update-InfisicalSecret',
'Remove-InfisicalSecret', 'Remove-InfisicalSecret',
'Copy-InfisicalSecret',
'ConvertTo-InfisicalSecretDictionary', 'ConvertTo-InfisicalSecretDictionary',
'Export-InfisicalSecrets', 'Export-InfisicalSecrets',
'Get-InfisicalProjects', 'Get-InfisicalProjects',
@@ -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<InfisicalConfigurationException>(() =>
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);
}
}
}
@@ -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"));
}
}
}
@@ -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<string> VerboseEntries { get; } = new List<string>();
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);
}
}
}
@@ -71,6 +71,10 @@ namespace PSInfisicalAPI.Tests
[InlineData(InfisicalEndpointNames.LdapAuthLogin, "POST", "/api/v1/auth/ldap-auth/login")] [InlineData(InfisicalEndpointNames.LdapAuthLogin, "POST", "/api/v1/auth/ldap-auth/login")]
[InlineData(InfisicalEndpointNames.AzureAuthLogin, "POST", "/api/v1/auth/azure-auth/login")] [InlineData(InfisicalEndpointNames.AzureAuthLogin, "POST", "/api/v1/auth/azure-auth/login")]
[InlineData(InfisicalEndpointNames.GcpIamAuthLogin, "POST", "/api/v1/auth/gcp-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) public void Registered_Endpoints_Have_Expected_Shape(string name, string method, string template)
{ {
InfisicalEndpointDefinition definition = InfisicalEndpointRegistry.Get(name); InfisicalEndpointDefinition definition = InfisicalEndpointRegistry.Get(name);
@@ -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);
}
}
}
}
@@ -21,8 +21,9 @@ namespace PSInfisicalAPI.Cmdlets
try try
{ {
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
InfisicalEnvironmentClient client = new InfisicalEnvironmentClient(HttpClient, Logger); InfisicalEnvironmentClient client = new InfisicalEnvironmentClient(HttpClient, Logger);
InfisicalEnvironment env = client.Retrieve(connection, ProjectId, EnvironmentSlugOrId); InfisicalEnvironment env = client.Retrieve(connection, resolvedProjectId, EnvironmentSlugOrId);
if (env != null) if (env != null)
{ {
WriteObject(env); WriteObject(env);
@@ -17,8 +17,9 @@ namespace PSInfisicalAPI.Cmdlets
try try
{ {
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
InfisicalEnvironmentClient client = new InfisicalEnvironmentClient(HttpClient, Logger); InfisicalEnvironmentClient client = new InfisicalEnvironmentClient(HttpClient, Logger);
InfisicalEnvironment[] envs = client.List(connection, ProjectId); InfisicalEnvironment[] envs = client.List(connection, resolvedProjectId);
foreach (InfisicalEnvironment env in envs) foreach (InfisicalEnvironment env in envs)
{ {
WriteObject(env); WriteObject(env);
@@ -23,8 +23,11 @@ namespace PSInfisicalAPI.Cmdlets
try try
{ {
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); 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); 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) if (folder != null)
{ {
WriteObject(folder); WriteObject(folder);
@@ -19,8 +19,11 @@ namespace PSInfisicalAPI.Cmdlets
try try
{ {
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); 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); 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) foreach (InfisicalFolder folder in folders)
{ {
WriteObject(folder); WriteObject(folder);
@@ -10,7 +10,7 @@ namespace PSInfisicalAPI.Cmdlets
[OutputType(typeof(InfisicalProject))] [OutputType(typeof(InfisicalProject))]
public sealed class GetInfisicalProjectCmdlet : InfisicalCmdletBase public sealed class GetInfisicalProjectCmdlet : InfisicalCmdletBase
{ {
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0)] [Parameter(ValueFromPipelineByPropertyName = true, Position = 0)]
[Alias("Id")] [Alias("Id")]
public string ProjectId { get; set; } public string ProjectId { get; set; }
@@ -19,8 +19,9 @@ namespace PSInfisicalAPI.Cmdlets
try try
{ {
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
InfisicalProjectClient client = new InfisicalProjectClient(HttpClient, Logger); InfisicalProjectClient client = new InfisicalProjectClient(HttpClient, Logger);
InfisicalProject project = client.Retrieve(connection, ProjectId); InfisicalProject project = client.Retrieve(connection, resolvedProjectId);
if (project != null) if (project != null)
{ {
WriteObject(project); WriteObject(project);
@@ -32,10 +32,10 @@ namespace PSInfisicalAPI.Cmdlets
InfisicalRetrieveSecretQuery query = new InfisicalRetrieveSecretQuery InfisicalRetrieveSecretQuery query = new InfisicalRetrieveSecretQuery
{ {
SecretName = SecretName, SecretName = SecretName,
ProjectId = ProjectId, ProjectId = ResolveProjectId(connection, ProjectId),
Environment = Environment, Environment = ResolveEnvironment(connection, Environment),
SecretPath = SecretPath, SecretPath = ResolveSecretPath(connection, SecretPath),
ApiVersion = ApiVersion, ApiVersion = ResolveApiVersion(connection, ApiVersion),
Version = Version, Version = Version,
Type = Type.ToString(), Type = Type.ToString(),
ViewSecretValue = ViewSecretValue.IsPresent, ViewSecretValue = ViewSecretValue.IsPresent,
@@ -32,10 +32,10 @@ namespace PSInfisicalAPI.Cmdlets
InfisicalListSecretsQuery query = new InfisicalListSecretsQuery InfisicalListSecretsQuery query = new InfisicalListSecretsQuery
{ {
ProjectId = ProjectId, ProjectId = ResolveProjectId(connection, ProjectId),
Environment = Environment, Environment = ResolveEnvironment(connection, Environment),
SecretPath = SecretPath, SecretPath = ResolveSecretPath(connection, SecretPath),
ApiVersion = ApiVersion, ApiVersion = ResolveApiVersion(connection, ApiVersion),
Recursive = Recursive.IsPresent, Recursive = Recursive.IsPresent,
IncludeImports = IncludeImports.IsPresent, IncludeImports = IncludeImports.IsPresent,
IncludePersonalOverrides = IncludePersonalOverrides.IsPresent, IncludePersonalOverrides = IncludePersonalOverrides.IsPresent,
@@ -21,8 +21,9 @@ namespace PSInfisicalAPI.Cmdlets
try try
{ {
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
InfisicalTagClient client = new InfisicalTagClient(HttpClient, Logger); InfisicalTagClient client = new InfisicalTagClient(HttpClient, Logger);
InfisicalTag tag = client.Retrieve(connection, ProjectId, TagSlugOrId); InfisicalTag tag = client.Retrieve(connection, resolvedProjectId, TagSlugOrId);
if (tag != null) if (tag != null)
{ {
WriteObject(tag); WriteObject(tag);
@@ -17,8 +17,9 @@ namespace PSInfisicalAPI.Cmdlets
try try
{ {
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
InfisicalTagClient client = new InfisicalTagClient(HttpClient, Logger); InfisicalTagClient client = new InfisicalTagClient(HttpClient, Logger);
InfisicalTag[] tags = client.List(connection, ProjectId); InfisicalTag[] tags = client.List(connection, resolvedProjectId);
foreach (InfisicalTag tag in tags) foreach (InfisicalTag tag in tags)
{ {
WriteObject(tag); WriteObject(tag);
@@ -1,5 +1,6 @@
using System; using System;
using System.Management.Automation; using System.Management.Automation;
using PSInfisicalAPI.Connections;
using PSInfisicalAPI.Errors; using PSInfisicalAPI.Errors;
using PSInfisicalAPI.Http; using PSInfisicalAPI.Http;
using PSInfisicalAPI.Logging; using PSInfisicalAPI.Logging;
@@ -44,5 +45,43 @@ namespace PSInfisicalAPI.Cmdlets
ErrorRecord record = InfisicalErrorHandler.ToErrorRecord(exception, details); ErrorRecord record = InfisicalErrorHandler.ToErrorRecord(exception, details);
ThrowTerminatingError(record); 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;
}
} }
} }
@@ -25,8 +25,9 @@ namespace PSInfisicalAPI.Cmdlets
} }
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
InfisicalEnvironmentClient client = new InfisicalEnvironmentClient(HttpClient, Logger); 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) if (env != null)
{ {
WriteObject(env); WriteObject(env);
@@ -25,8 +25,11 @@ namespace PSInfisicalAPI.Cmdlets
} }
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); 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); 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) if (folder != null)
{ {
WriteObject(folder); WriteObject(folder);
@@ -1,4 +1,5 @@
using System; using System;
using System.Collections;
using System.Management.Automation; using System.Management.Automation;
using System.Security; using System.Security;
using PSInfisicalAPI.Connections; using PSInfisicalAPI.Connections;
@@ -12,7 +13,9 @@ namespace PSInfisicalAPI.Cmdlets
[OutputType(typeof(InfisicalSecret))] [OutputType(typeof(InfisicalSecret))]
public sealed class NewInfisicalSecretCmdlet : InfisicalCmdletBase 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")] [Parameter(Mandatory = true, Position = 1, ParameterSetName = "PlainText")]
public string SecretValue { get; set; } public string SecretValue { get; set; }
@@ -20,6 +23,9 @@ namespace PSInfisicalAPI.Cmdlets
[Parameter(Mandatory = true, Position = 1, ParameterSetName = "SecureString")] [Parameter(Mandatory = true, Position = 1, ParameterSetName = "SecureString")]
public SecureString SecureSecretValue { get; set; } 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 SecretComment { get; set; }
[Parameter] public string ProjectId { get; set; } [Parameter] public string ProjectId { get; set; }
[Parameter] public string Environment { get; set; } [Parameter] public string Environment { get; set; }
@@ -33,35 +39,62 @@ namespace PSInfisicalAPI.Cmdlets
{ {
try 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; return;
} }
if (!ShouldProcess(SecretName, "Create Infisical secret")) { return; }
string plainValue = SecureSecretValue != null string plainValue = SecureSecretValue != null
? SecureStringUtility.UsePlainText(SecureSecretValue, p => p) ? SecureStringUtility.UsePlainText(SecureSecretValue, p => p)
: SecretValue; : SecretValue;
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
InfisicalCreateSecretRequest request = new InfisicalCreateSecretRequest InfisicalCreateSecretRequest request = new InfisicalCreateSecretRequest
{ {
SecretName = SecretName, SecretName = SecretName,
SecretValue = plainValue, SecretValue = plainValue,
SecretComment = SecretComment, SecretComment = SecretComment,
ProjectId = ProjectId, ProjectId = resolvedProjectId,
Environment = Environment, Environment = resolvedEnvironment,
SecretPath = SecretPath, SecretPath = resolvedSecretPath,
Type = Type.ToString(), Type = Type.ToString(),
ApiVersion = ApiVersion, ApiVersion = resolvedApiVersion,
SkipMultilineEncoding = SkipMultilineEncoding.IsPresent ? (bool?)true : null, SkipMultilineEncoding = SkipMultilineEncoding.IsPresent ? (bool?)true : null,
TagIds = TagIds TagIds = TagIds
}; };
InfisicalSecretsClient client = new InfisicalSecretsClient(HttpClient, Logger); InfisicalSecretsClient client = new InfisicalSecretsClient(HttpClient, Logger);
InfisicalSecret secret = client.Create(connection, request); InfisicalSecret single = client.Create(connection, request);
if (secret != null) if (single != null)
{ {
WriteObject(secret); WriteObject(single);
} }
} }
catch (Exception exception) catch (Exception exception)
@@ -25,8 +25,9 @@ namespace PSInfisicalAPI.Cmdlets
} }
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
InfisicalTagClient client = new InfisicalTagClient(HttpClient, Logger); 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) if (tag != null)
{ {
WriteObject(tag); WriteObject(tag);
@@ -25,8 +25,9 @@ namespace PSInfisicalAPI.Cmdlets
} }
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
InfisicalEnvironmentClient client = new InfisicalEnvironmentClient(HttpClient, Logger); InfisicalEnvironmentClient client = new InfisicalEnvironmentClient(HttpClient, Logger);
client.Delete(connection, ProjectId, EnvironmentId); client.Delete(connection, resolvedProjectId, EnvironmentId);
if (PassThru.IsPresent) if (PassThru.IsPresent)
{ {
@@ -27,8 +27,11 @@ namespace PSInfisicalAPI.Cmdlets
} }
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); 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); InfisicalFolderClient client = new InfisicalFolderClient(HttpClient, Logger);
client.Delete(connection, ProjectId, Environment, FolderId, Path); client.Delete(connection, resolvedProjectId, resolvedEnvironment, FolderId, resolvedPath);
if (PassThru.IsPresent) if (PassThru.IsPresent)
{ {
@@ -8,7 +8,7 @@ namespace PSInfisicalAPI.Cmdlets
[Cmdlet(VerbsCommon.Remove, "InfisicalProject", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)] [Cmdlet(VerbsCommon.Remove, "InfisicalProject", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)]
public sealed class RemoveInfisicalProjectCmdlet : InfisicalCmdletBase public sealed class RemoveInfisicalProjectCmdlet : InfisicalCmdletBase
{ {
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0)] [Parameter(ValueFromPipelineByPropertyName = true, Position = 0)]
[Alias("Id")] [Alias("Id")]
public string ProjectId { get; set; } public string ProjectId { get; set; }
@@ -18,18 +18,20 @@ namespace PSInfisicalAPI.Cmdlets
{ {
try try
{ {
if (!ShouldProcess(ProjectId, "Remove Infisical project")) InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
if (!ShouldProcess(resolvedProjectId, "Remove Infisical project"))
{ {
return; return;
} }
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
InfisicalProjectClient client = new InfisicalProjectClient(HttpClient, Logger); InfisicalProjectClient client = new InfisicalProjectClient(HttpClient, Logger);
client.Delete(connection, ProjectId); client.Delete(connection, resolvedProjectId);
if (PassThru.IsPresent) if (PassThru.IsPresent)
{ {
WriteObject(ProjectId); WriteObject(resolvedProjectId);
} }
} }
catch (Exception exception) catch (Exception exception)
@@ -6,12 +6,16 @@ using PSInfisicalAPI.Secrets;
namespace PSInfisicalAPI.Cmdlets 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 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; } 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 ProjectId { get; set; }
[Parameter] public string Environment { get; set; } [Parameter] public string Environment { get; set; }
[Parameter] public string SecretPath { get; set; } [Parameter] public string SecretPath { get; set; }
@@ -23,23 +27,51 @@ namespace PSInfisicalAPI.Cmdlets
{ {
try 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; return;
} }
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); if (!ShouldProcess(SecretName, "Remove Infisical secret")) { return; }
InfisicalDeleteSecretRequest request = new InfisicalDeleteSecretRequest InfisicalDeleteSecretRequest request = new InfisicalDeleteSecretRequest
{ {
SecretName = SecretName, SecretName = SecretName,
ProjectId = ProjectId, ProjectId = resolvedProjectId,
Environment = Environment, Environment = resolvedEnvironment,
SecretPath = SecretPath, SecretPath = resolvedSecretPath,
Type = Type.ToString(), Type = Type.ToString(),
ApiVersion = ApiVersion ApiVersion = resolvedApiVersion
}; };
InfisicalSecretsClient client = new InfisicalSecretsClient(HttpClient, Logger);
client.Delete(connection, request); client.Delete(connection, request);
if (PassThru.IsPresent) if (PassThru.IsPresent)
@@ -25,8 +25,9 @@ namespace PSInfisicalAPI.Cmdlets
} }
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
InfisicalTagClient client = new InfisicalTagClient(HttpClient, Logger); InfisicalTagClient client = new InfisicalTagClient(HttpClient, Logger);
client.Delete(connection, ProjectId, TagId); client.Delete(connection, resolvedProjectId, TagId);
if (PassThru.IsPresent) if (PassThru.IsPresent)
{ {
@@ -29,8 +29,9 @@ namespace PSInfisicalAPI.Cmdlets
} }
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
InfisicalEnvironmentClient client = new InfisicalEnvironmentClient(HttpClient, Logger); 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) if (env != null)
{ {
WriteObject(env); WriteObject(env);
@@ -29,8 +29,11 @@ namespace PSInfisicalAPI.Cmdlets
} }
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); 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); 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) if (folder != null)
{ {
WriteObject(folder); WriteObject(folder);
@@ -10,7 +10,7 @@ namespace PSInfisicalAPI.Cmdlets
[OutputType(typeof(InfisicalProject))] [OutputType(typeof(InfisicalProject))]
public sealed class UpdateInfisicalProjectCmdlet : InfisicalCmdletBase public sealed class UpdateInfisicalProjectCmdlet : InfisicalCmdletBase
{ {
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0)] [Parameter(ValueFromPipelineByPropertyName = true, Position = 0)]
[Alias("Id")] [Alias("Id")]
public string ProjectId { get; set; } public string ProjectId { get; set; }
@@ -22,14 +22,16 @@ namespace PSInfisicalAPI.Cmdlets
{ {
try try
{ {
if (!ShouldProcess(ProjectId, "Update Infisical project")) InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
if (!ShouldProcess(resolvedProjectId, "Update Infisical project"))
{ {
return; return;
} }
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
InfisicalProjectClient client = new InfisicalProjectClient(HttpClient, Logger); 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) if (project != null)
{ {
WriteObject(project); WriteObject(project);
@@ -1,4 +1,5 @@
using System; using System;
using System.Collections;
using System.Management.Automation; using System.Management.Automation;
using System.Security; using System.Security;
using PSInfisicalAPI.Connections; using PSInfisicalAPI.Connections;
@@ -12,12 +13,16 @@ namespace PSInfisicalAPI.Cmdlets
[OutputType(typeof(InfisicalSecret))] [OutputType(typeof(InfisicalSecret))]
public sealed class UpdateInfisicalSecretCmdlet : InfisicalCmdletBase 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; } public string SecretName { get; set; }
[Parameter(ParameterSetName = "PlainText")] public string SecretValue { get; set; } [Parameter(ParameterSetName = "PlainText")] public string SecretValue { get; set; }
[Parameter(ParameterSetName = "SecureString")] public SecureString SecureSecretValue { 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 NewSecretName { get; set; }
[Parameter] public string SecretComment { get; set; } [Parameter] public string SecretComment { get; set; }
[Parameter] public string ProjectId { get; set; } [Parameter] public string ProjectId { get; set; }
@@ -32,36 +37,63 @@ namespace PSInfisicalAPI.Cmdlets
{ {
try 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; return;
} }
if (!ShouldProcess(SecretName, "Update Infisical secret")) { return; }
string plainValue = SecureSecretValue != null string plainValue = SecureSecretValue != null
? SecureStringUtility.UsePlainText(SecureSecretValue, p => p) ? SecureStringUtility.UsePlainText(SecureSecretValue, p => p)
: SecretValue; : SecretValue;
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
InfisicalUpdateSecretRequest request = new InfisicalUpdateSecretRequest InfisicalUpdateSecretRequest request = new InfisicalUpdateSecretRequest
{ {
SecretName = SecretName, SecretName = SecretName,
NewSecretName = NewSecretName, NewSecretName = NewSecretName,
SecretValue = plainValue, SecretValue = plainValue,
SecretComment = SecretComment, SecretComment = SecretComment,
ProjectId = ProjectId, ProjectId = resolvedProjectId,
Environment = Environment, Environment = resolvedEnvironment,
SecretPath = SecretPath, SecretPath = resolvedSecretPath,
Type = Type.ToString(), Type = Type.ToString(),
ApiVersion = ApiVersion, ApiVersion = resolvedApiVersion,
SkipMultilineEncoding = SkipMultilineEncoding.IsPresent ? (bool?)true : null, SkipMultilineEncoding = SkipMultilineEncoding.IsPresent ? (bool?)true : null,
TagIds = TagIds TagIds = TagIds
}; };
InfisicalSecretsClient client = new InfisicalSecretsClient(HttpClient, Logger); InfisicalSecretsClient client = new InfisicalSecretsClient(HttpClient, Logger);
InfisicalSecret secret = client.Update(connection, request); InfisicalSecret single = client.Update(connection, request);
if (secret != null) if (single != null)
{ {
WriteObject(secret); WriteObject(single);
} }
} }
catch (Exception exception) catch (Exception exception)
@@ -29,8 +29,9 @@ namespace PSInfisicalAPI.Cmdlets
} }
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent(); InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
InfisicalTagClient client = new InfisicalTagClient(HttpClient, Logger); 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) if (tag != null)
{ {
WriteObject(tag); WriteObject(tag);
@@ -15,6 +15,10 @@ namespace PSInfisicalAPI.Endpoints
public const string CreateSecret = "CreateSecret"; public const string CreateSecret = "CreateSecret";
public const string UpdateSecret = "UpdateSecret"; public const string UpdateSecret = "UpdateSecret";
public const string DeleteSecret = "DeleteSecret"; 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 ListProjects = "ListProjects";
public const string RetrieveProject = "RetrieveProject"; public const string RetrieveProject = "RetrieveProject";
@@ -197,6 +197,54 @@ namespace PSInfisicalAPI.Endpoints
RequiresAuthorization = true, RequiresAuthorization = true,
ContainsSecretMaterialInResponse = 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<string, List<InfisicalEndpointDefinition>> map) private static void RegisterProjects(Dictionary<string, List<InfisicalEndpointDefinition>> map)
@@ -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<InfisicalBulkCreateSecretItem> list = new List<InfisicalBulkCreateSecretItem>();
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<InfisicalBulkUpdateSecretItem> list = new List<InfisicalBulkUpdateSecretItem>();
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<string> items = new List<string>();
foreach (object item in enumerable) { if (item != null) { items.Add(item.ToString()); } }
return items.ToArray();
}
return new[] { value.ToString() };
}
private static Dictionary<string, string> 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<string, string> result = new Dictionary<string, string>(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;
}
}
}
@@ -88,4 +88,75 @@ namespace PSInfisicalAPI.Secrets
[JsonProperty("secretPath", NullValueHandling = NullValueHandling.Ignore)] public string SecretPath { get; set; } [JsonProperty("secretPath", NullValueHandling = NullValueHandling.Ignore)] public string SecretPath { get; set; }
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)] public string Type { 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<InfisicalSecretMetadataDto> 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<InfisicalSecretMetadataDto> 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<InfisicalSecretBatchCreateItemDto> 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<InfisicalSecretBatchUpdateItemDto> 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<InfisicalSecretBatchDeleteItemDto> 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; }
}
} }
@@ -69,4 +69,69 @@ namespace PSInfisicalAPI.Secrets
public string Type { get; set; } public string Type { get; set; }
public string ApiVersion { 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<string, string> 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<string, string> 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; }
}
} }
@@ -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<InfisicalSecretBatchCreateItemDto> items = new List<InfisicalSecretBatchCreateItemDto>(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<InfisicalSecretListResponseDto>(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<InfisicalSecretBatchUpdateItemDto> items = new List<InfisicalSecretBatchUpdateItemDto>(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<InfisicalSecretListResponseDto>(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<InfisicalSecretBatchDeleteItemDto> items = new List<InfisicalSecretBatchDeleteItemDto>(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<InfisicalSecretListResponseDto>(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<InfisicalSecretMetadataDto> ToMetadataDtoList(Dictionary<string, string> metadata)
{
if (metadata == null || metadata.Count == 0) { return null; }
List<InfisicalSecretMetadataDto> list = new List<InfisicalSecretMetadataDto>(metadata.Count);
foreach (KeyValuePair<string, string> kvp in metadata)
{
list.Add(new InfisicalSecretMetadataDto { Key = kvp.Key, Value = kvp.Value });
}
return list;
}
public void Delete(InfisicalConnection connection, InfisicalDeleteSecretRequest request) public void Delete(InfisicalConnection connection, InfisicalDeleteSecretRequest request)
{ {
if (connection == null) { throw new ArgumentNullException(nameof(connection)); } if (connection == null) { throw new ArgumentNullException(nameof(connection)); }