Tests: roll forward to latest major .NET runtime #3
@@ -6,6 +6,53 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) loos
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 2026.06.04.0020
|
||||
|
||||
- Build produced from commit 211fbcf34dbb.
|
||||
|
||||
## Unreleased (carried forward)
|
||||
|
||||
## 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<string, string>[]` instead of `Hashtable[]`. `InfisicalBulkSecretConverter` accepts `IEnumerable<IDictionary<string, string>>` 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**:
|
||||
- **Bulk parameter sets** added to `New-InfisicalSecret`, `Update-InfisicalSecret`, and `Remove-InfisicalSecret` accepting `-Secrets Hashtable[]`; client methods `CreateBatch`/`UpdateBatch`/`DeleteBatch` wrap `POST|PATCH|DELETE /api/v3/secrets/batch/raw`.
|
||||
- **`Copy-InfisicalSecret`** cmdlet added, wrapping `POST /api/v4/secrets/duplicate` with source/destination environment + path parameters and per-attribute copy toggles.
|
||||
- **Connection inheritance** centralized in `InfisicalCmdletBase` (`ResolveProjectId`/`ResolveEnvironment`/`ResolveSecretPath`/`ResolveApiVersion`/`ResolveOrganizationId`). Explicit parameters always win; missing values fall back to the active connection and emit a `-Verbose` line.
|
||||
- Project/Environment/Folder/Tag and all secret cmdlets refactored to use the inheritance helpers; existing explicit-parameter behavior is preserved.
|
||||
- `InfisicalBulkSecretConverter` accepts flexible key aliases (`SecretName`/`Name`/`Key`, `SecretValue`/`Value`, `SecretComment`/`Comment`, `Metadata`/`SecretMetadata`).
|
||||
- Test count: 161 (up from 139). Added coverage for bulk DTO shapes, the converter, the duplicate request DTO, registry entries for the four new endpoints, and the resolution helpers.
|
||||
|
||||
## Unreleased (carried forward)
|
||||
|
||||
## 2026.06.03.2206
|
||||
|
||||
- Build produced from commit 09c3d5c68bbc.
|
||||
|
||||
## Unreleased (carried forward)
|
||||
|
||||
## 2026.06.03.2136
|
||||
|
||||
- Build produced from commit d9822aab7a4a.
|
||||
- **Resource CRUD expansion**: Added full Get/New/Update/Remove cmdlet families for Projects, Environments, Folders, and Tags (20 new cmdlets):
|
||||
- Projects: `Get-InfisicalProjects`, `Get-InfisicalProject`, `New-InfisicalProject`, `Update-InfisicalProject`, `Remove-InfisicalProject`.
|
||||
- Environments: `Get-InfisicalEnvironments`, `Get-InfisicalEnvironment`, `New-InfisicalEnvironment`, `Update-InfisicalEnvironment`, `Remove-InfisicalEnvironment`.
|
||||
- Folders: `Get-InfisicalFolders`, `Get-InfisicalFolder`, `New-InfisicalFolder`, `Update-InfisicalFolder`, `Remove-InfisicalFolder`.
|
||||
- Tags: `Get-InfisicalTags`, `Get-InfisicalTag`, `New-InfisicalTag`, `Update-InfisicalTag`, `Remove-InfisicalTag`.
|
||||
- **Secret mutation cmdlets**: Added `New-InfisicalSecret`, `Update-InfisicalSecret`, and `Remove-InfisicalSecret`; extended `InfisicalSecretsClient` with corresponding create/update/delete operations.
|
||||
- **Additional auth providers**: `Connect-Infisical` now supports JWT (`-Jwt -IdentityId`), OIDC (`-Jwt -IdentityId`), LDAP (`-Username -Password`), Azure (`-Jwt -IdentityId`), and GCP IAM (`-Jwt -IdentityId`) via dedicated parameter sets. Common identity-login flow is centralized in `IdentityLoginExecutor`.
|
||||
- Endpoint registry expanded with login routes (`/api/v1/auth/{jwt|oidc|ldap|azure|gcp}-auth/login`) and CRUD routes for projects (v2), environments, folders, tags, and secret mutations.
|
||||
- Test suite expanded to 139 passing tests, including mapper round-trips for projects/environments/folders/tags, secret mutation DTO shapes, and request-body validation for each new auth provider.
|
||||
|
||||
## 2026.06.03.0131
|
||||
|
||||
- Build produced from commit 7be0b7b42008.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@{
|
||||
RootModule = 'PSInfisicalAPI.psm1'
|
||||
ModuleVersion = '2026.06.03.0131'
|
||||
ModuleVersion = '2026.06.04.0020'
|
||||
GUID = 'b8a2f3d4-7c51-4d2f-9e6a-1f0c8b3d4e51'
|
||||
Author = 'Grace Solutions'
|
||||
CompanyName = 'Grace Solutions'
|
||||
@@ -14,8 +14,32 @@
|
||||
'Disconnect-Infisical',
|
||||
'Get-InfisicalSecrets',
|
||||
'Get-InfisicalSecret',
|
||||
'New-InfisicalSecret',
|
||||
'Update-InfisicalSecret',
|
||||
'Remove-InfisicalSecret',
|
||||
'Copy-InfisicalSecret',
|
||||
'ConvertTo-InfisicalSecretDictionary',
|
||||
'Export-InfisicalSecrets'
|
||||
'Export-InfisicalSecrets',
|
||||
'Get-InfisicalProjects',
|
||||
'Get-InfisicalProject',
|
||||
'New-InfisicalProject',
|
||||
'Update-InfisicalProject',
|
||||
'Remove-InfisicalProject',
|
||||
'Get-InfisicalEnvironments',
|
||||
'Get-InfisicalEnvironment',
|
||||
'New-InfisicalEnvironment',
|
||||
'Update-InfisicalEnvironment',
|
||||
'Remove-InfisicalEnvironment',
|
||||
'Get-InfisicalFolders',
|
||||
'Get-InfisicalFolder',
|
||||
'New-InfisicalFolder',
|
||||
'Update-InfisicalFolder',
|
||||
'Remove-InfisicalFolder',
|
||||
'Get-InfisicalTags',
|
||||
'Get-InfisicalTag',
|
||||
'New-InfisicalTag',
|
||||
'Update-InfisicalTag',
|
||||
'Remove-InfisicalTag'
|
||||
)
|
||||
AliasesToExport = @()
|
||||
VariablesToExport = @()
|
||||
@@ -27,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 = '7be0b7b42008'
|
||||
CommitHash = '211fbcf34dbb'
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -102,8 +102,32 @@ function Write-Manifest {
|
||||
'Disconnect-Infisical',
|
||||
'Get-InfisicalSecrets',
|
||||
'Get-InfisicalSecret',
|
||||
'New-InfisicalSecret',
|
||||
'Update-InfisicalSecret',
|
||||
'Remove-InfisicalSecret',
|
||||
'Copy-InfisicalSecret',
|
||||
'ConvertTo-InfisicalSecretDictionary',
|
||||
'Export-InfisicalSecrets'
|
||||
'Export-InfisicalSecrets',
|
||||
'Get-InfisicalProjects',
|
||||
'Get-InfisicalProject',
|
||||
'New-InfisicalProject',
|
||||
'Update-InfisicalProject',
|
||||
'Remove-InfisicalProject',
|
||||
'Get-InfisicalEnvironments',
|
||||
'Get-InfisicalEnvironment',
|
||||
'New-InfisicalEnvironment',
|
||||
'Update-InfisicalEnvironment',
|
||||
'Remove-InfisicalEnvironment',
|
||||
'Get-InfisicalFolders',
|
||||
'Get-InfisicalFolder',
|
||||
'New-InfisicalFolder',
|
||||
'Update-InfisicalFolder',
|
||||
'Remove-InfisicalFolder',
|
||||
'Get-InfisicalTags',
|
||||
'Get-InfisicalTag',
|
||||
'New-InfisicalTag',
|
||||
'Update-InfisicalTag',
|
||||
'Remove-InfisicalTag'
|
||||
)
|
||||
AliasesToExport = @()
|
||||
VariablesToExport = @()
|
||||
@@ -163,7 +187,7 @@ if (`$null -eq `$manifest) {
|
||||
|
||||
Import-Module -Name '$($ModuleDirectory.FullName)' -Force
|
||||
|
||||
`$cmds = @('Connect-Infisical','Disconnect-Infisical','Get-InfisicalSecrets','Get-InfisicalSecret','ConvertTo-InfisicalSecretDictionary','Export-InfisicalSecrets')
|
||||
`$cmds = @('Connect-Infisical','Disconnect-Infisical','Get-InfisicalSecrets','Get-InfisicalSecret','New-InfisicalSecret','Update-InfisicalSecret','Remove-InfisicalSecret','ConvertTo-InfisicalSecretDictionary','Export-InfisicalSecrets','Get-InfisicalProjects','Get-InfisicalProject','New-InfisicalProject','Update-InfisicalProject','Remove-InfisicalProject','Get-InfisicalEnvironments','Get-InfisicalEnvironment','New-InfisicalEnvironment','Update-InfisicalEnvironment','Remove-InfisicalEnvironment','Get-InfisicalFolders','Get-InfisicalFolder','New-InfisicalFolder','Update-InfisicalFolder','Remove-InfisicalFolder','Get-InfisicalTags','Get-InfisicalTag','New-InfisicalTag','Update-InfisicalTag','Remove-InfisicalTag')
|
||||
foreach (`$c in `$cmds) {
|
||||
if (-not (Get-Command -Name `$c -Module PSInfisicalAPI -ErrorAction SilentlyContinue)) {
|
||||
throw "Cmdlet not found: `$c"
|
||||
|
||||
+59
-11
@@ -6,15 +6,39 @@
|
||||
|
||||
The goal is to establish a strong, reusable, secure framework first, then initially implement secret retrieval and export workflows.
|
||||
|
||||
Initial public cmdlets:
|
||||
Public cmdlets:
|
||||
|
||||
```powershell
|
||||
Connect-Infisical
|
||||
Disconnect-Infisical
|
||||
Get-InfisicalSecrets
|
||||
Get-InfisicalSecret
|
||||
New-InfisicalSecret
|
||||
Update-InfisicalSecret
|
||||
Remove-InfisicalSecret
|
||||
Copy-InfisicalSecret
|
||||
ConvertTo-InfisicalSecretDictionary
|
||||
Export-InfisicalSecrets
|
||||
Get-InfisicalProjects
|
||||
Get-InfisicalProject
|
||||
New-InfisicalProject
|
||||
Update-InfisicalProject
|
||||
Remove-InfisicalProject
|
||||
Get-InfisicalEnvironments
|
||||
Get-InfisicalEnvironment
|
||||
New-InfisicalEnvironment
|
||||
Update-InfisicalEnvironment
|
||||
Remove-InfisicalEnvironment
|
||||
Get-InfisicalFolders
|
||||
Get-InfisicalFolder
|
||||
New-InfisicalFolder
|
||||
Update-InfisicalFolder
|
||||
Remove-InfisicalFolder
|
||||
Get-InfisicalTags
|
||||
Get-InfisicalTag
|
||||
New-InfisicalTag
|
||||
Update-InfisicalTag
|
||||
Remove-InfisicalTag
|
||||
```
|
||||
|
||||
Infisical’s public API is REST-based and provides programmatic access for managing secrets and related resources. Current Infisical documentation shows the list-secrets endpoint under `/api/v4/secrets`, the single-secret retrieval endpoint under `/api/v4/secrets/{secretName}`, and Universal Auth login under `/api/v1/auth/universal-auth/login`. The implementation must centralize API endpoint definitions because Infisical uses different API versions across resource families. ([Infisical Blog][1])
|
||||
@@ -198,8 +222,32 @@ Example shape:
|
||||
'Disconnect-Infisical',
|
||||
'Get-InfisicalSecrets',
|
||||
'Get-InfisicalSecret',
|
||||
'New-InfisicalSecret',
|
||||
'Update-InfisicalSecret',
|
||||
'Remove-InfisicalSecret',
|
||||
'Copy-InfisicalSecret',
|
||||
'ConvertTo-InfisicalSecretDictionary',
|
||||
'Export-InfisicalSecrets'
|
||||
'Export-InfisicalSecrets',
|
||||
'Get-InfisicalProjects',
|
||||
'Get-InfisicalProject',
|
||||
'New-InfisicalProject',
|
||||
'Update-InfisicalProject',
|
||||
'Remove-InfisicalProject',
|
||||
'Get-InfisicalEnvironments',
|
||||
'Get-InfisicalEnvironment',
|
||||
'New-InfisicalEnvironment',
|
||||
'Update-InfisicalEnvironment',
|
||||
'Remove-InfisicalEnvironment',
|
||||
'Get-InfisicalFolders',
|
||||
'Get-InfisicalFolder',
|
||||
'New-InfisicalFolder',
|
||||
'Update-InfisicalFolder',
|
||||
'Remove-InfisicalFolder',
|
||||
'Get-InfisicalTags',
|
||||
'Get-InfisicalTag',
|
||||
'New-InfisicalTag',
|
||||
'Update-InfisicalTag',
|
||||
'Remove-InfisicalTag'
|
||||
)
|
||||
AliasesToExport = @()
|
||||
PrivateData = @{
|
||||
@@ -698,29 +746,29 @@ Internal implementation must still use proper typed path handling.
|
||||
|
||||
# 12. Authentication Design
|
||||
|
||||
## 12.1 Supported Initial Auth Types
|
||||
## 12.1 Supported Auth Types
|
||||
|
||||
Initial implementation:
|
||||
Currently implemented:
|
||||
|
||||
```text
|
||||
Universal Auth
|
||||
Token Auth
|
||||
JWT Auth
|
||||
OIDC Auth
|
||||
LDAP Auth
|
||||
Azure Auth
|
||||
GCP IAM Auth
|
||||
```
|
||||
|
||||
Infisical documents identity authentication modes such as Universal Auth and Token Auth for API access, and API interaction requires an access token. ([Infisical Blog][3])
|
||||
Each implemented provider is exposed as a dedicated `Connect-Infisical` parameter set. Identity-based providers (JWT, OIDC, Azure, GCP IAM) share a common login flow via `IdentityLoginExecutor` and POST to `/api/v1/auth/{provider}-auth/login`. Infisical documents identity authentication modes such as Universal Auth and Token Auth for API access, and API interaction requires an access token. ([Infisical Blog][3])
|
||||
|
||||
## 12.2 Future Auth Types
|
||||
|
||||
Design must allow future support for:
|
||||
|
||||
```text
|
||||
AWS Auth
|
||||
Azure Auth
|
||||
GCP Auth
|
||||
AWS IAM Auth
|
||||
Kubernetes Auth
|
||||
OIDC Auth
|
||||
JWT Auth
|
||||
LDAP Auth
|
||||
TLS Certificate Auth
|
||||
Alibaba Cloud Auth
|
||||
OCI Auth
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using PSInfisicalAPI.Authentication;
|
||||
using PSInfisicalAPI.Errors;
|
||||
using PSInfisicalAPI.Http;
|
||||
using PSInfisicalAPI.Security;
|
||||
using Xunit;
|
||||
|
||||
namespace PSInfisicalAPI.Tests
|
||||
{
|
||||
public class AuthProviderTests
|
||||
{
|
||||
private sealed class CapturingHttpClient : IInfisicalHttpClient
|
||||
{
|
||||
public InfisicalHttpRequest CapturedRequest { get; private set; }
|
||||
public string ResponseBody { get; set; } = "{\"accessToken\":\"abc.def.ghi\",\"expiresIn\":3600,\"tokenType\":\"Bearer\"}";
|
||||
|
||||
public InfisicalHttpResponse Send(InfisicalHttpRequest request)
|
||||
{
|
||||
CapturedRequest = request;
|
||||
return new InfisicalHttpResponse
|
||||
{
|
||||
StatusCode = 200,
|
||||
Body = ResponseBody,
|
||||
Headers = new Dictionary<string, string>()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static InfisicalAuthenticationRequest BaseRequest()
|
||||
{
|
||||
return new InfisicalAuthenticationRequest
|
||||
{
|
||||
BaseUri = new Uri("https://example.invalid"),
|
||||
ApiVersion = "v1"
|
||||
};
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void JwtAuthProvider_Posts_IdentityId_And_Jwt()
|
||||
{
|
||||
CapturingHttpClient http = new CapturingHttpClient();
|
||||
InfisicalAuthenticationRequest request = BaseRequest();
|
||||
request.IdentityId = "identity-1";
|
||||
request.Jwt = SecureStringUtility.ToReadOnlySecureString("token.value");
|
||||
|
||||
InfisicalAuthenticationResult result = new JwtAuthProvider().Authenticate(request, http, null);
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.NotNull(http.CapturedRequest);
|
||||
Assert.Equal("POST", http.CapturedRequest.Method);
|
||||
Assert.EndsWith("/api/v1/auth/jwt-auth/login", http.CapturedRequest.Uri.AbsolutePath);
|
||||
JObject body = JObject.Parse(http.CapturedRequest.Body);
|
||||
Assert.Equal("identity-1", (string)body["identityId"]);
|
||||
Assert.Equal("token.value", (string)body["jwt"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OidcAuthProvider_Posts_IdentityId_And_Jwt_To_Oidc_Endpoint()
|
||||
{
|
||||
CapturingHttpClient http = new CapturingHttpClient();
|
||||
InfisicalAuthenticationRequest request = BaseRequest();
|
||||
request.IdentityId = "identity-2";
|
||||
request.Jwt = SecureStringUtility.ToReadOnlySecureString("oidc.token");
|
||||
|
||||
new OidcAuthProvider().Authenticate(request, http, null);
|
||||
|
||||
Assert.EndsWith("/api/v1/auth/oidc-auth/login", http.CapturedRequest.Uri.AbsolutePath);
|
||||
JObject body = JObject.Parse(http.CapturedRequest.Body);
|
||||
Assert.Equal("identity-2", (string)body["identityId"]);
|
||||
Assert.Equal("oidc.token", (string)body["jwt"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LdapAuthProvider_Posts_Username_And_Password_To_Ldap_Endpoint()
|
||||
{
|
||||
CapturingHttpClient http = new CapturingHttpClient();
|
||||
InfisicalAuthenticationRequest request = BaseRequest();
|
||||
request.Username = "svc.account";
|
||||
request.Password = SecureStringUtility.ToReadOnlySecureString("P@ssw0rd!");
|
||||
|
||||
new LdapAuthProvider().Authenticate(request, http, null);
|
||||
|
||||
Assert.EndsWith("/api/v1/auth/ldap-auth/login", http.CapturedRequest.Uri.AbsolutePath);
|
||||
JObject body = JObject.Parse(http.CapturedRequest.Body);
|
||||
Assert.Equal("svc.account", (string)body["username"]);
|
||||
Assert.Equal("P@ssw0rd!", (string)body["password"]);
|
||||
Assert.False(body.ContainsKey("identityId"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LdapAuthProvider_Includes_IdentityId_When_Supplied()
|
||||
{
|
||||
CapturingHttpClient http = new CapturingHttpClient();
|
||||
InfisicalAuthenticationRequest request = BaseRequest();
|
||||
request.Username = "u";
|
||||
request.Password = SecureStringUtility.ToReadOnlySecureString("p");
|
||||
request.IdentityId = "id-ldap";
|
||||
|
||||
new LdapAuthProvider().Authenticate(request, http, null);
|
||||
|
||||
JObject body = JObject.Parse(http.CapturedRequest.Body);
|
||||
Assert.Equal("id-ldap", (string)body["identityId"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AzureAuthProvider_Posts_IdentityId_And_Jwt_To_Azure_Endpoint()
|
||||
{
|
||||
CapturingHttpClient http = new CapturingHttpClient();
|
||||
InfisicalAuthenticationRequest request = BaseRequest();
|
||||
request.IdentityId = "identity-az";
|
||||
request.Jwt = SecureStringUtility.ToReadOnlySecureString("az.token");
|
||||
|
||||
new AzureAuthProvider().Authenticate(request, http, null);
|
||||
|
||||
Assert.EndsWith("/api/v1/auth/azure-auth/login", http.CapturedRequest.Uri.AbsolutePath);
|
||||
JObject body = JObject.Parse(http.CapturedRequest.Body);
|
||||
Assert.Equal("identity-az", (string)body["identityId"]);
|
||||
Assert.Equal("az.token", (string)body["jwt"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GcpIamAuthProvider_Posts_IdentityId_And_Jwt_To_Gcp_Endpoint()
|
||||
{
|
||||
CapturingHttpClient http = new CapturingHttpClient();
|
||||
InfisicalAuthenticationRequest request = BaseRequest();
|
||||
request.IdentityId = "identity-gcp";
|
||||
request.Jwt = SecureStringUtility.ToReadOnlySecureString("gcp.token");
|
||||
|
||||
new GcpIamAuthProvider().Authenticate(request, http, null);
|
||||
|
||||
Assert.EndsWith("/api/v1/auth/gcp-auth/login", http.CapturedRequest.Uri.AbsolutePath);
|
||||
JObject body = JObject.Parse(http.CapturedRequest.Body);
|
||||
Assert.Equal("identity-gcp", (string)body["identityId"]);
|
||||
Assert.Equal("gcp.token", (string)body["jwt"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void JwtAuthProvider_Throws_When_IdentityId_Missing()
|
||||
{
|
||||
InfisicalAuthenticationRequest request = BaseRequest();
|
||||
request.Jwt = SecureStringUtility.ToReadOnlySecureString("x");
|
||||
Assert.Throws<InfisicalAuthenticationException>(() =>
|
||||
new JwtAuthProvider().Authenticate(request, new CapturingHttpClient(), null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LdapAuthProvider_Throws_When_Password_Missing()
|
||||
{
|
||||
InfisicalAuthenticationRequest request = BaseRequest();
|
||||
request.Username = "u";
|
||||
Assert.Throws<InfisicalAuthenticationException>(() =>
|
||||
new LdapAuthProvider().Authenticate(request, new CapturingHttpClient(), null));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Management.Automation;
|
||||
using PSInfisicalAPI.Cmdlets;
|
||||
using PSInfisicalAPI.Errors;
|
||||
using PSInfisicalAPI.Secrets;
|
||||
using Xunit;
|
||||
|
||||
namespace PSInfisicalAPI.Tests
|
||||
{
|
||||
public class BulkSecretConverterTests
|
||||
{
|
||||
[Fact]
|
||||
public void ToCreateItems_Maps_Standard_Keys()
|
||||
{
|
||||
Dictionary<string, string> entry = new Dictionary<string, string>
|
||||
{
|
||||
{ "SecretName", "API_KEY" },
|
||||
{ "SecretValue", "abc" },
|
||||
{ "SecretComment", "primary" },
|
||||
{ "SkipMultilineEncoding", "true" },
|
||||
{ "TagIds", "tag-1,tag-2" }
|
||||
};
|
||||
|
||||
InfisicalBulkCreateSecretItem[] items = InfisicalBulkSecretConverter.ToCreateItems(new IDictionary<string, string>[] { 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()
|
||||
{
|
||||
Dictionary<string, string> entry = new Dictionary<string, string>
|
||||
{
|
||||
{ "Name", "API_KEY" },
|
||||
{ "Value", "abc" }
|
||||
};
|
||||
|
||||
InfisicalBulkCreateSecretItem[] items = InfisicalBulkSecretConverter.ToCreateItems(new IDictionary<string, string>[] { entry });
|
||||
Assert.Single(items);
|
||||
Assert.Equal("API_KEY", items[0].SecretName);
|
||||
Assert.Equal("abc", items[0].SecretValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToCreateItems_Without_SecretName_Throws()
|
||||
{
|
||||
Dictionary<string, string> entry = new Dictionary<string, string> { { "Value", "abc" } };
|
||||
|
||||
Assert.Throws<InfisicalConfigurationException>(() =>
|
||||
InfisicalBulkSecretConverter.ToCreateItems(new IDictionary<string, string>[] { entry }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToUpdateItems_Maps_NewSecretName()
|
||||
{
|
||||
Dictionary<string, string> entry = new Dictionary<string, string>
|
||||
{
|
||||
{ "SecretName", "API_KEY" },
|
||||
{ "NewSecretName", "RENAMED" },
|
||||
{ "SecretValue", "new-value" }
|
||||
};
|
||||
|
||||
InfisicalBulkUpdateSecretItem[] items = InfisicalBulkSecretConverter.ToUpdateItems(new IDictionary<string, string>[] { 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_Trims_TagId_Whitespace_And_Skips_Empty()
|
||||
{
|
||||
Dictionary<string, string> entry = new Dictionary<string, string>
|
||||
{
|
||||
{ "SecretName", "API_KEY" },
|
||||
{ "SecretValue", "abc" },
|
||||
{ "TagIds", " tag-1 , , tag-2 " }
|
||||
};
|
||||
|
||||
InfisicalBulkCreateSecretItem[] items = InfisicalBulkSecretConverter.ToCreateItems(new IDictionary<string, string>[] { entry });
|
||||
Assert.Equal(new[] { "tag-1", "tag-2" }, items[0].TagIds);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToCreateItems_Empty_Input_Returns_Empty()
|
||||
{
|
||||
InfisicalBulkCreateSecretItem[] items = InfisicalBulkSecretConverter.ToCreateItems(null);
|
||||
Assert.Empty(items);
|
||||
}
|
||||
}
|
||||
|
||||
public class BulkSecretsTransformationAttributeTests
|
||||
{
|
||||
private static IDictionary<string, string>[] Transform(object input)
|
||||
{
|
||||
BulkSecretsTransformationAttribute attribute = new BulkSecretsTransformationAttribute();
|
||||
object result = attribute.Transform(null, input);
|
||||
return (IDictionary<string, string>[])result;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Transform_Single_Hashtable_Wraps_Into_Array()
|
||||
{
|
||||
Hashtable input = new Hashtable { { "SecretName", "API_KEY" }, { "SecretValue", "abc" } };
|
||||
|
||||
IDictionary<string, string>[] result = Transform(input);
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal("API_KEY", result[0]["SecretName"]);
|
||||
Assert.Equal("abc", result[0]["SecretValue"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Transform_Hashtable_Array_Preserves_Order()
|
||||
{
|
||||
Hashtable a = new Hashtable { { "SecretName", "A" }, { "SecretValue", "1" } };
|
||||
Hashtable b = new Hashtable { { "SecretName", "B" }, { "SecretValue", "2" } };
|
||||
|
||||
IDictionary<string, string>[] result = Transform(new object[] { a, b });
|
||||
|
||||
Assert.Equal(2, result.Length);
|
||||
Assert.Equal("A", result[0]["SecretName"]);
|
||||
Assert.Equal("B", result[1]["SecretName"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Transform_OrderedDictionary_Converts_To_StringString()
|
||||
{
|
||||
OrderedDictionary input = new OrderedDictionary();
|
||||
input.Add("SecretName", "API_KEY");
|
||||
input.Add("SkipMultilineEncoding", true);
|
||||
|
||||
IDictionary<string, string>[] result = Transform(input);
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal("API_KEY", result[0]["SecretName"]);
|
||||
Assert.Equal("true", result[0]["SkipMultilineEncoding"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Transform_Array_Values_Are_Joined_Comma_Separated()
|
||||
{
|
||||
Hashtable input = new Hashtable
|
||||
{
|
||||
{ "SecretName", "API_KEY" },
|
||||
{ "TagIds", new[] { "tag-1", "tag-2" } }
|
||||
};
|
||||
|
||||
IDictionary<string, string>[] result = Transform(input);
|
||||
|
||||
Assert.Equal("tag-1,tag-2", result[0]["TagIds"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Transform_Already_Typed_Array_Passes_Through()
|
||||
{
|
||||
IDictionary<string, string>[] input = new IDictionary<string, string>[]
|
||||
{
|
||||
new Dictionary<string, string> { { "SecretName", "A" }, { "SecretValue", "1" } }
|
||||
};
|
||||
|
||||
IDictionary<string, string>[] result = Transform(input);
|
||||
|
||||
Assert.Same(input, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Transform_Null_Input_Returns_Null()
|
||||
{
|
||||
BulkSecretsTransformationAttribute attribute = new BulkSecretsTransformationAttribute();
|
||||
Assert.Null(attribute.Transform(null, null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Transform_Invalid_Element_Throws_ArgumentTransformationMetadataException()
|
||||
{
|
||||
BulkSecretsTransformationAttribute attribute = new BulkSecretsTransformationAttribute();
|
||||
Assert.Throws<ArgumentTransformationMetadataException>(() =>
|
||||
attribute.Transform(null, new object[] { "not-a-dictionary" }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Transform_End_To_End_With_Converter_Produces_Bulk_Items()
|
||||
{
|
||||
Hashtable entry = new Hashtable
|
||||
{
|
||||
{ "SecretName", "API_KEY" },
|
||||
{ "SecretValue", "abc" },
|
||||
{ "TagIds", new[] { "tag-1", "tag-2" } },
|
||||
{ "SkipMultilineEncoding", true }
|
||||
};
|
||||
|
||||
IDictionary<string, string>[] transformed = Transform(new object[] { entry });
|
||||
InfisicalBulkCreateSecretItem[] items = InfisicalBulkSecretConverter.ToCreateItems(transformed);
|
||||
|
||||
Assert.Single(items);
|
||||
Assert.Equal("API_KEY", items[0].SecretName);
|
||||
Assert.Equal("abc", items[0].SecretValue);
|
||||
Assert.Equal(new[] { "tag-1", "tag-2" }, items[0].TagIds);
|
||||
Assert.True(items[0].SkipMultilineEncoding);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
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("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"];
|
||||
Assert.Single(secrets);
|
||||
Assert.Equal("K1", (string)secrets[0]["secretKey"]);
|
||||
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()
|
||||
{
|
||||
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("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"]);
|
||||
}
|
||||
|
||||
[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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,5 +45,55 @@ namespace PSInfisicalAPI.Tests
|
||||
{
|
||||
Assert.Throws<InfisicalConfigurationException>(() => InfisicalEndpointRegistry.Get("NotARealEndpoint"));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(InfisicalEndpointNames.CreateSecret, "POST", "/api/v3/secrets/raw/{secretName}")]
|
||||
[InlineData(InfisicalEndpointNames.UpdateSecret, "PATCH", "/api/v3/secrets/raw/{secretName}")]
|
||||
[InlineData(InfisicalEndpointNames.DeleteSecret, "DELETE", "/api/v3/secrets/raw/{secretName}")]
|
||||
[InlineData(InfisicalEndpointNames.ListProjects, "GET", "/api/v1/workspace")]
|
||||
[InlineData(InfisicalEndpointNames.RetrieveProject, "GET", "/api/v1/workspace/{projectId}")]
|
||||
[InlineData(InfisicalEndpointNames.CreateProject, "POST", "/api/v2/workspace")]
|
||||
[InlineData(InfisicalEndpointNames.UpdateProject, "PATCH", "/api/v1/workspace/{projectId}")]
|
||||
[InlineData(InfisicalEndpointNames.DeleteProject, "DELETE", "/api/v1/workspace/{projectId}")]
|
||||
[InlineData(InfisicalEndpointNames.CreateEnvironment, "POST", "/api/v1/workspace/{projectId}/environments")]
|
||||
[InlineData(InfisicalEndpointNames.UpdateEnvironment, "PATCH", "/api/v1/workspace/{projectId}/environments/{environmentId}")]
|
||||
[InlineData(InfisicalEndpointNames.DeleteEnvironment, "DELETE", "/api/v1/workspace/{projectId}/environments/{environmentId}")]
|
||||
[InlineData(InfisicalEndpointNames.ListFolders, "GET", "/api/v1/folders")]
|
||||
[InlineData(InfisicalEndpointNames.CreateFolder, "POST", "/api/v1/folders")]
|
||||
[InlineData(InfisicalEndpointNames.UpdateFolder, "PATCH", "/api/v1/folders/{folderId}")]
|
||||
[InlineData(InfisicalEndpointNames.DeleteFolder, "DELETE", "/api/v1/folders/{folderId}")]
|
||||
[InlineData(InfisicalEndpointNames.ListTags, "GET", "/api/v1/workspace/{projectId}/tags")]
|
||||
[InlineData(InfisicalEndpointNames.CreateTag, "POST", "/api/v1/workspace/{projectId}/tags")]
|
||||
[InlineData(InfisicalEndpointNames.UpdateTag, "PATCH", "/api/v1/workspace/{projectId}/tags/{tagId}")]
|
||||
[InlineData(InfisicalEndpointNames.DeleteTag, "DELETE", "/api/v1/workspace/{projectId}/tags/{tagId}")]
|
||||
[InlineData(InfisicalEndpointNames.JwtAuthLogin, "POST", "/api/v1/auth/jwt-auth/login")]
|
||||
[InlineData(InfisicalEndpointNames.OidcAuthLogin, "POST", "/api/v1/auth/oidc-auth/login")]
|
||||
[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/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)
|
||||
{
|
||||
InfisicalEndpointDefinition definition = InfisicalEndpointRegistry.Get(name);
|
||||
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<InfisicalEndpointDefinition> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
using System.Reflection;
|
||||
using PSInfisicalAPI.Models;
|
||||
using Xunit;
|
||||
|
||||
namespace PSInfisicalAPI.Tests
|
||||
{
|
||||
public class EnvironmentMapperTests
|
||||
{
|
||||
private static readonly System.Type MapperType = typeof(PSInfisicalAPI.Connections.InfisicalConnection).Assembly
|
||||
.GetType("PSInfisicalAPI.Environments.InfisicalEnvironmentMapper", true);
|
||||
|
||||
private static readonly System.Type DtoType = typeof(PSInfisicalAPI.Connections.InfisicalConnection).Assembly
|
||||
.GetType("PSInfisicalAPI.Environments.InfisicalEnvironmentResponseDto", true);
|
||||
|
||||
private static InfisicalEnvironment InvokeMap(object dto, string fallbackProjectId)
|
||||
{
|
||||
MethodInfo map = MapperType.GetMethod("Map", BindingFlags.Public | BindingFlags.Static);
|
||||
return (InfisicalEnvironment)map.Invoke(null, new object[] { dto, fallbackProjectId });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Map_Null_Returns_Null()
|
||||
{
|
||||
Assert.Null(InvokeMap(null, "proj-x"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Map_Populates_Fields_With_Explicit_ProjectId()
|
||||
{
|
||||
object dto = System.Activator.CreateInstance(DtoType);
|
||||
DtoType.GetProperty("Id").SetValue(dto, "env-001");
|
||||
DtoType.GetProperty("Name").SetValue(dto, "Production");
|
||||
DtoType.GetProperty("Slug").SetValue(dto, "prod");
|
||||
DtoType.GetProperty("Position").SetValue(dto, 1);
|
||||
DtoType.GetProperty("ProjectId").SetValue(dto, "proj-001");
|
||||
|
||||
InfisicalEnvironment env = InvokeMap(dto, "fallback-proj");
|
||||
|
||||
Assert.Equal("env-001", env.Id);
|
||||
Assert.Equal("Production", env.Name);
|
||||
Assert.Equal("prod", env.Slug);
|
||||
Assert.Equal(1, env.Position);
|
||||
Assert.Equal("proj-001", env.ProjectId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Map_Uses_WorkspaceId_When_ProjectId_Empty()
|
||||
{
|
||||
object dto = System.Activator.CreateInstance(DtoType);
|
||||
DtoType.GetProperty("Id").SetValue(dto, "env-002");
|
||||
DtoType.GetProperty("WorkspaceId").SetValue(dto, "wks-002");
|
||||
|
||||
InfisicalEnvironment env = InvokeMap(dto, "fallback-proj");
|
||||
Assert.Equal("wks-002", env.ProjectId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Map_Uses_Fallback_When_No_ProjectId_Or_WorkspaceId()
|
||||
{
|
||||
object dto = System.Activator.CreateInstance(DtoType);
|
||||
DtoType.GetProperty("Id").SetValue(dto, "env-003");
|
||||
|
||||
InfisicalEnvironment env = InvokeMap(dto, "fallback-proj");
|
||||
Assert.Equal("fallback-proj", env.ProjectId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Map_Falls_Back_To_InternalId_For_Id()
|
||||
{
|
||||
object dto = System.Activator.CreateInstance(DtoType);
|
||||
DtoType.GetProperty("InternalId").SetValue(dto, "internal-env");
|
||||
|
||||
InfisicalEnvironment env = InvokeMap(dto, "p");
|
||||
Assert.Equal("internal-env", env.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
using System.Reflection;
|
||||
using PSInfisicalAPI.Models;
|
||||
using Xunit;
|
||||
|
||||
namespace PSInfisicalAPI.Tests
|
||||
{
|
||||
public class FolderMapperTests
|
||||
{
|
||||
private static readonly System.Type MapperType = typeof(PSInfisicalAPI.Connections.InfisicalConnection).Assembly
|
||||
.GetType("PSInfisicalAPI.Folders.InfisicalFolderMapper", true);
|
||||
|
||||
private static readonly System.Type DtoType = typeof(PSInfisicalAPI.Connections.InfisicalConnection).Assembly
|
||||
.GetType("PSInfisicalAPI.Folders.InfisicalFolderResponseDto", true);
|
||||
|
||||
private static InfisicalFolder InvokeMap(object dto, string fallbackProjectId, string fallbackEnvironment)
|
||||
{
|
||||
MethodInfo map = MapperType.GetMethod("Map", BindingFlags.Public | BindingFlags.Static);
|
||||
return (InfisicalFolder)map.Invoke(null, new object[] { dto, fallbackProjectId, fallbackEnvironment });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Map_Null_Returns_Null()
|
||||
{
|
||||
Assert.Null(InvokeMap(null, "proj-x", "dev"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Map_Populates_Fields_With_Explicit_ProjectId()
|
||||
{
|
||||
object dto = System.Activator.CreateInstance(DtoType);
|
||||
DtoType.GetProperty("Id").SetValue(dto, "fld-001");
|
||||
DtoType.GetProperty("Name").SetValue(dto, "config");
|
||||
DtoType.GetProperty("Path").SetValue(dto, "/app/config");
|
||||
DtoType.GetProperty("ParentId").SetValue(dto, "fld-root");
|
||||
DtoType.GetProperty("Environment").SetValue(dto, "prod");
|
||||
DtoType.GetProperty("ProjectId").SetValue(dto, "proj-001");
|
||||
|
||||
InfisicalFolder folder = InvokeMap(dto, "fallback-proj", "fallback-env");
|
||||
|
||||
Assert.Equal("fld-001", folder.Id);
|
||||
Assert.Equal("config", folder.Name);
|
||||
Assert.Equal("/app/config", folder.Path);
|
||||
Assert.Equal("fld-root", folder.ParentId);
|
||||
Assert.Equal("prod", folder.Environment);
|
||||
Assert.Equal("proj-001", folder.ProjectId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Map_Uses_WorkspaceId_When_ProjectId_Empty()
|
||||
{
|
||||
object dto = System.Activator.CreateInstance(DtoType);
|
||||
DtoType.GetProperty("Id").SetValue(dto, "fld-002");
|
||||
DtoType.GetProperty("WorkspaceId").SetValue(dto, "wks-002");
|
||||
|
||||
InfisicalFolder folder = InvokeMap(dto, "fallback-proj", "fallback-env");
|
||||
Assert.Equal("wks-002", folder.ProjectId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Map_Uses_Fallback_When_No_ProjectId_Or_Environment()
|
||||
{
|
||||
object dto = System.Activator.CreateInstance(DtoType);
|
||||
DtoType.GetProperty("Id").SetValue(dto, "fld-003");
|
||||
|
||||
InfisicalFolder folder = InvokeMap(dto, "fallback-proj", "fallback-env");
|
||||
Assert.Equal("fallback-proj", folder.ProjectId);
|
||||
Assert.Equal("fallback-env", folder.Environment);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Map_Falls_Back_To_InternalId_For_Id()
|
||||
{
|
||||
object dto = System.Activator.CreateInstance(DtoType);
|
||||
DtoType.GetProperty("InternalId").SetValue(dto, "internal-fld");
|
||||
|
||||
InfisicalFolder folder = InvokeMap(dto, "p", "e");
|
||||
Assert.Equal("internal-fld", folder.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<RollForward>LatestMajor</RollForward>
|
||||
<LangVersion>9.0</LangVersion>
|
||||
<IsPackable>false</IsPackable>
|
||||
<AssemblyName>PSInfisicalAPI.Tests</AssemblyName>
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
using System.Collections;
|
||||
using System.Reflection;
|
||||
using PSInfisicalAPI.Models;
|
||||
using Xunit;
|
||||
|
||||
namespace PSInfisicalAPI.Tests
|
||||
{
|
||||
public class ProjectMapperTests
|
||||
{
|
||||
private static readonly System.Type MapperType = typeof(PSInfisicalAPI.Connections.InfisicalConnection).Assembly
|
||||
.GetType("PSInfisicalAPI.Projects.InfisicalProjectMapper", true);
|
||||
|
||||
private static readonly System.Type DtoType = typeof(PSInfisicalAPI.Connections.InfisicalConnection).Assembly
|
||||
.GetType("PSInfisicalAPI.Projects.InfisicalProjectResponseDto", true);
|
||||
|
||||
private static readonly System.Type EnvDtoType = typeof(PSInfisicalAPI.Connections.InfisicalConnection).Assembly
|
||||
.GetType("PSInfisicalAPI.Projects.InfisicalProjectEnvironmentDto", true);
|
||||
|
||||
private static InfisicalProject InvokeMap(object dto)
|
||||
{
|
||||
MethodInfo map = MapperType.GetMethod("Map", BindingFlags.Public | BindingFlags.Static);
|
||||
return (InfisicalProject)map.Invoke(null, new[] { dto });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Map_Null_Dto_Returns_Null()
|
||||
{
|
||||
Assert.Null(InvokeMap(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Map_Populates_Core_Fields()
|
||||
{
|
||||
object dto = System.Activator.CreateInstance(DtoType);
|
||||
DtoType.GetProperty("Id").SetValue(dto, "proj-001");
|
||||
DtoType.GetProperty("Name").SetValue(dto, "DevOps");
|
||||
DtoType.GetProperty("Slug").SetValue(dto, "devops");
|
||||
DtoType.GetProperty("Description").SetValue(dto, "Internal DevOps project");
|
||||
DtoType.GetProperty("Organization").SetValue(dto, "org-abc");
|
||||
DtoType.GetProperty("Type").SetValue(dto, "secret-manager");
|
||||
DtoType.GetProperty("AutoCapitalization").SetValue(dto, true);
|
||||
DtoType.GetProperty("CreatedAt").SetValue(dto, "2026-01-15T12:34:56Z");
|
||||
DtoType.GetProperty("UpdatedAt").SetValue(dto, "2026-02-20T09:00:00Z");
|
||||
|
||||
InfisicalProject project = InvokeMap(dto);
|
||||
|
||||
Assert.Equal("proj-001", project.Id);
|
||||
Assert.Equal("DevOps", project.Name);
|
||||
Assert.Equal("devops", project.Slug);
|
||||
Assert.Equal("Internal DevOps project", project.Description);
|
||||
Assert.Equal("org-abc", project.OrganizationId);
|
||||
Assert.Equal("secret-manager", project.Type);
|
||||
Assert.True(project.AutoCapitalization);
|
||||
Assert.NotNull(project.CreatedAtUtc);
|
||||
Assert.NotNull(project.UpdatedAtUtc);
|
||||
Assert.Empty(project.EnvironmentSlugs);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Map_Falls_Back_To_InternalId_And_OrgId()
|
||||
{
|
||||
object dto = System.Activator.CreateInstance(DtoType);
|
||||
DtoType.GetProperty("InternalId").SetValue(dto, "internal-id-1");
|
||||
DtoType.GetProperty("OrgId").SetValue(dto, "org-fallback");
|
||||
|
||||
InfisicalProject project = InvokeMap(dto);
|
||||
|
||||
Assert.Equal("internal-id-1", project.Id);
|
||||
Assert.Equal("org-fallback", project.OrganizationId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Map_Extracts_EnvironmentSlugs()
|
||||
{
|
||||
object dto = System.Activator.CreateInstance(DtoType);
|
||||
DtoType.GetProperty("Id").SetValue(dto, "proj-002");
|
||||
|
||||
System.Type listType = typeof(System.Collections.Generic.List<>).MakeGenericType(EnvDtoType);
|
||||
IList envs = (IList)System.Activator.CreateInstance(listType);
|
||||
|
||||
object env1 = System.Activator.CreateInstance(EnvDtoType);
|
||||
EnvDtoType.GetProperty("Slug").SetValue(env1, "dev");
|
||||
EnvDtoType.GetProperty("Name").SetValue(env1, "Development");
|
||||
envs.Add(env1);
|
||||
|
||||
object env2 = System.Activator.CreateInstance(EnvDtoType);
|
||||
EnvDtoType.GetProperty("Slug").SetValue(env2, "prod");
|
||||
envs.Add(env2);
|
||||
|
||||
DtoType.GetProperty("Environments").SetValue(dto, envs);
|
||||
|
||||
InfisicalProject project = InvokeMap(dto);
|
||||
|
||||
Assert.Equal(2, project.EnvironmentSlugs.Length);
|
||||
Assert.Contains("dev", project.EnvironmentSlugs);
|
||||
Assert.Contains("prod", project.EnvironmentSlugs);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MapMany_Null_Returns_Empty()
|
||||
{
|
||||
MethodInfo mapMany = MapperType.GetMethod("MapMany", BindingFlags.Public | BindingFlags.Static);
|
||||
InfisicalProject[] result = (InfisicalProject[])mapMany.Invoke(null, new object[] { null });
|
||||
Assert.NotNull(result);
|
||||
Assert.Empty(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using System.Reflection;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace PSInfisicalAPI.Tests
|
||||
{
|
||||
public class SecretMutationDtoTests
|
||||
{
|
||||
private static readonly System.Reflection.Assembly ModuleAssembly =
|
||||
typeof(PSInfisicalAPI.Connections.InfisicalConnection).Assembly;
|
||||
|
||||
private static object MakeDto(string typeName)
|
||||
{
|
||||
System.Type t = ModuleAssembly.GetType(typeName, true);
|
||||
return System.Activator.CreateInstance(t);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateRequestDto_Serializes_With_Expected_Field_Names()
|
||||
{
|
||||
object dto = MakeDto("PSInfisicalAPI.Secrets.InfisicalSecretCreateRequestDto");
|
||||
dto.GetType().GetProperty("WorkspaceId").SetValue(dto, "wks-1");
|
||||
dto.GetType().GetProperty("Environment").SetValue(dto, "prod");
|
||||
dto.GetType().GetProperty("SecretPath").SetValue(dto, "/db");
|
||||
dto.GetType().GetProperty("Type").SetValue(dto, "shared");
|
||||
dto.GetType().GetProperty("SecretValue").SetValue(dto, "p@ss");
|
||||
dto.GetType().GetProperty("SecretComment").SetValue(dto, "comment");
|
||||
|
||||
JObject json = JObject.Parse(JsonConvert.SerializeObject(dto));
|
||||
Assert.Equal("wks-1", (string)json["workspaceId"]);
|
||||
Assert.Equal("prod", (string)json["environment"]);
|
||||
Assert.Equal("/db", (string)json["secretPath"]);
|
||||
Assert.Equal("shared", (string)json["type"]);
|
||||
Assert.Equal("p@ss", (string)json["secretValue"]);
|
||||
Assert.Equal("comment", (string)json["secretComment"]);
|
||||
Assert.False(json.ContainsKey("skipMultilineEncoding"));
|
||||
Assert.False(json.ContainsKey("tagIds"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UpdateRequestDto_Omits_Null_Optional_Fields()
|
||||
{
|
||||
object dto = MakeDto("PSInfisicalAPI.Secrets.InfisicalSecretUpdateRequestDto");
|
||||
dto.GetType().GetProperty("WorkspaceId").SetValue(dto, "wks-1");
|
||||
dto.GetType().GetProperty("Environment").SetValue(dto, "prod");
|
||||
dto.GetType().GetProperty("NewSecretName").SetValue(dto, "renamed");
|
||||
|
||||
JObject json = JObject.Parse(JsonConvert.SerializeObject(dto));
|
||||
Assert.Equal("renamed", (string)json["newSecretName"]);
|
||||
Assert.False(json.ContainsKey("secretValue"));
|
||||
Assert.False(json.ContainsKey("secretComment"));
|
||||
Assert.False(json.ContainsKey("type"));
|
||||
Assert.False(json.ContainsKey("secretPath"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeleteRequestDto_Serializes_With_Expected_Field_Names()
|
||||
{
|
||||
object dto = MakeDto("PSInfisicalAPI.Secrets.InfisicalSecretDeleteRequestDto");
|
||||
dto.GetType().GetProperty("WorkspaceId").SetValue(dto, "wks-1");
|
||||
dto.GetType().GetProperty("Environment").SetValue(dto, "prod");
|
||||
dto.GetType().GetProperty("SecretPath").SetValue(dto, "/db");
|
||||
dto.GetType().GetProperty("Type").SetValue(dto, "shared");
|
||||
|
||||
JObject json = JObject.Parse(JsonConvert.SerializeObject(dto));
|
||||
Assert.Equal("wks-1", (string)json["workspaceId"]);
|
||||
Assert.Equal("prod", (string)json["environment"]);
|
||||
Assert.Equal("/db", (string)json["secretPath"]);
|
||||
Assert.Equal("shared", (string)json["type"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
using System.Reflection;
|
||||
using PSInfisicalAPI.Models;
|
||||
using Xunit;
|
||||
|
||||
namespace PSInfisicalAPI.Tests
|
||||
{
|
||||
public class TagMapperTests
|
||||
{
|
||||
private static readonly System.Type MapperType = typeof(PSInfisicalAPI.Connections.InfisicalConnection).Assembly
|
||||
.GetType("PSInfisicalAPI.Tags.InfisicalTagMapper", true);
|
||||
|
||||
private static readonly System.Type DtoType = typeof(PSInfisicalAPI.Connections.InfisicalConnection).Assembly
|
||||
.GetType("PSInfisicalAPI.Tags.InfisicalTagResponseDto", true);
|
||||
|
||||
private static InfisicalTag InvokeMap(object dto, string fallbackProjectId)
|
||||
{
|
||||
MethodInfo map = MapperType.GetMethod("Map", BindingFlags.Public | BindingFlags.Static);
|
||||
return (InfisicalTag)map.Invoke(null, new object[] { dto, fallbackProjectId });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Map_Null_Returns_Null()
|
||||
{
|
||||
Assert.Null(InvokeMap(null, "proj-x"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Map_Populates_Fields_With_Explicit_ProjectId()
|
||||
{
|
||||
object dto = System.Activator.CreateInstance(DtoType);
|
||||
DtoType.GetProperty("Id").SetValue(dto, "tag-001");
|
||||
DtoType.GetProperty("Slug").SetValue(dto, "critical");
|
||||
DtoType.GetProperty("Name").SetValue(dto, "Critical");
|
||||
DtoType.GetProperty("Color").SetValue(dto, "#FF0000");
|
||||
DtoType.GetProperty("ProjectId").SetValue(dto, "proj-001");
|
||||
|
||||
InfisicalTag tag = InvokeMap(dto, "fallback-proj");
|
||||
|
||||
Assert.Equal("tag-001", tag.Id);
|
||||
Assert.Equal("critical", tag.Slug);
|
||||
Assert.Equal("Critical", tag.Name);
|
||||
Assert.Equal("#FF0000", tag.Color);
|
||||
Assert.Equal("proj-001", tag.ProjectId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Map_Uses_WorkspaceId_When_ProjectId_Empty()
|
||||
{
|
||||
object dto = System.Activator.CreateInstance(DtoType);
|
||||
DtoType.GetProperty("Id").SetValue(dto, "tag-002");
|
||||
DtoType.GetProperty("WorkspaceId").SetValue(dto, "wks-002");
|
||||
|
||||
InfisicalTag tag = InvokeMap(dto, "fallback-proj");
|
||||
Assert.Equal("wks-002", tag.ProjectId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Map_Uses_Fallback_When_No_ProjectId_Or_WorkspaceId()
|
||||
{
|
||||
object dto = System.Activator.CreateInstance(DtoType);
|
||||
DtoType.GetProperty("Id").SetValue(dto, "tag-003");
|
||||
|
||||
InfisicalTag tag = InvokeMap(dto, "fallback-proj");
|
||||
Assert.Equal("fallback-proj", tag.ProjectId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Map_Falls_Back_To_InternalId_For_Id()
|
||||
{
|
||||
object dto = System.Activator.CreateInstance(DtoType);
|
||||
DtoType.GetProperty("InternalId").SetValue(dto, "internal-tag");
|
||||
|
||||
InfisicalTag tag = InvokeMap(dto, "p");
|
||||
Assert.Equal("internal-tag", tag.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System.Collections.Generic;
|
||||
using PSInfisicalAPI.Endpoints;
|
||||
using PSInfisicalAPI.Errors;
|
||||
using PSInfisicalAPI.Http;
|
||||
using PSInfisicalAPI.Logging;
|
||||
using PSInfisicalAPI.Security;
|
||||
|
||||
namespace PSInfisicalAPI.Authentication
|
||||
{
|
||||
public sealed class AzureAuthProvider : IInfisicalAuthProvider
|
||||
{
|
||||
private const string Component = "AzureAuthProvider";
|
||||
|
||||
public string Name { get { return "AzureAuth"; } }
|
||||
|
||||
public InfisicalAuthenticationResult Authenticate(InfisicalAuthenticationRequest request, IInfisicalHttpClient httpClient, IInfisicalLogger logger)
|
||||
{
|
||||
if (request == null || string.IsNullOrEmpty(request.IdentityId))
|
||||
{
|
||||
throw new InfisicalAuthenticationException("IdentityId is required for Azure Auth.");
|
||||
}
|
||||
|
||||
if (request.Jwt == null || request.Jwt.Length == 0)
|
||||
{
|
||||
throw new InfisicalAuthenticationException("Jwt is required for Azure Auth.");
|
||||
}
|
||||
|
||||
return IdentityLoginExecutor.Execute(InfisicalEndpointNames.AzureAuthLogin, Component, request, httpClient, logger, serializer =>
|
||||
{
|
||||
return SecureStringUtility.UsePlainText(request.Jwt, plainJwt =>
|
||||
{
|
||||
Dictionary<string, string> bodyObject = new Dictionary<string, string>
|
||||
{
|
||||
{ "identityId", request.IdentityId },
|
||||
{ "jwt", plainJwt ?? string.Empty }
|
||||
};
|
||||
|
||||
return serializer.Serialize(bodyObject);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System.Collections.Generic;
|
||||
using PSInfisicalAPI.Endpoints;
|
||||
using PSInfisicalAPI.Errors;
|
||||
using PSInfisicalAPI.Http;
|
||||
using PSInfisicalAPI.Logging;
|
||||
using PSInfisicalAPI.Security;
|
||||
|
||||
namespace PSInfisicalAPI.Authentication
|
||||
{
|
||||
public sealed class GcpIamAuthProvider : IInfisicalAuthProvider
|
||||
{
|
||||
private const string Component = "GcpIamAuthProvider";
|
||||
|
||||
public string Name { get { return "GcpIamAuth"; } }
|
||||
|
||||
public InfisicalAuthenticationResult Authenticate(InfisicalAuthenticationRequest request, IInfisicalHttpClient httpClient, IInfisicalLogger logger)
|
||||
{
|
||||
if (request == null || string.IsNullOrEmpty(request.IdentityId))
|
||||
{
|
||||
throw new InfisicalAuthenticationException("IdentityId is required for GCP IAM Auth.");
|
||||
}
|
||||
|
||||
if (request.Jwt == null || request.Jwt.Length == 0)
|
||||
{
|
||||
throw new InfisicalAuthenticationException("Jwt is required for GCP IAM Auth.");
|
||||
}
|
||||
|
||||
return IdentityLoginExecutor.Execute(InfisicalEndpointNames.GcpIamAuthLogin, Component, request, httpClient, logger, serializer =>
|
||||
{
|
||||
return SecureStringUtility.UsePlainText(request.Jwt, plainJwt =>
|
||||
{
|
||||
Dictionary<string, string> bodyObject = new Dictionary<string, string>
|
||||
{
|
||||
{ "identityId", request.IdentityId },
|
||||
{ "jwt", plainJwt ?? string.Empty }
|
||||
};
|
||||
|
||||
return serializer.Serialize(bodyObject);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Security;
|
||||
using PSInfisicalAPI.Endpoints;
|
||||
using PSInfisicalAPI.Errors;
|
||||
using PSInfisicalAPI.Http;
|
||||
using PSInfisicalAPI.Logging;
|
||||
using PSInfisicalAPI.Security;
|
||||
using PSInfisicalAPI.Serialization;
|
||||
|
||||
namespace PSInfisicalAPI.Authentication
|
||||
{
|
||||
internal static class IdentityLoginExecutor
|
||||
{
|
||||
internal static InfisicalAuthenticationResult Execute(
|
||||
string endpointName,
|
||||
string component,
|
||||
InfisicalAuthenticationRequest request,
|
||||
IInfisicalHttpClient httpClient,
|
||||
IInfisicalLogger logger,
|
||||
Func<JsonInfisicalSerializer, string> bodyFactory)
|
||||
{
|
||||
if (request == null) { throw new ArgumentNullException(nameof(request)); }
|
||||
if (httpClient == null) { throw new ArgumentNullException(nameof(httpClient)); }
|
||||
if (bodyFactory == null) { throw new ArgumentNullException(nameof(bodyFactory)); }
|
||||
|
||||
IInfisicalLogger log = logger ?? NullInfisicalLogger.Instance;
|
||||
log.Information(component, "Attempting to authenticate to Infisical. Please Wait...");
|
||||
|
||||
InfisicalEndpointDefinition definition = InfisicalEndpointRegistry.Get(endpointName);
|
||||
Uri uri = InfisicalUriBuilder.Build(request.BaseUri, definition, null, null);
|
||||
JsonInfisicalSerializer serializer = new JsonInfisicalSerializer();
|
||||
string body = bodyFactory(serializer);
|
||||
|
||||
InfisicalHttpRequest httpRequest = new InfisicalHttpRequest
|
||||
{
|
||||
OperationName = "Authenticate",
|
||||
EndpointName = definition.Name,
|
||||
Method = definition.Method,
|
||||
Uri = uri,
|
||||
Body = body,
|
||||
ContentType = "application/json",
|
||||
ContainsSecretMaterialInRequest = definition.ContainsSecretMaterialInRequest,
|
||||
ContainsSecretMaterialInResponse = definition.ContainsSecretMaterialInResponse,
|
||||
Headers = new Dictionary<string, string> { { "Accept", "application/json" } }
|
||||
};
|
||||
|
||||
InfisicalHttpResponse response = httpClient.Send(httpRequest);
|
||||
|
||||
try
|
||||
{
|
||||
if (response.StatusCode < 200 || response.StatusCode >= 300)
|
||||
{
|
||||
log.Error(component, "Infisical authentication failed.");
|
||||
throw new InfisicalAuthenticationException(string.Concat(component, " login returned status ", response.StatusCode.ToString(CultureInfo.InvariantCulture), "."));
|
||||
}
|
||||
|
||||
IdentityLoginResponse parsed = serializer.Deserialize<IdentityLoginResponse>(response.Body);
|
||||
if (parsed == null || string.IsNullOrEmpty(parsed.AccessToken))
|
||||
{
|
||||
throw new InfisicalAuthenticationException(string.Concat(component, " login response did not contain an access token."));
|
||||
}
|
||||
|
||||
SecureString accessToken = SecureStringUtility.ToReadOnlySecureString(parsed.AccessToken);
|
||||
|
||||
DateTimeOffset? expiresAt = null;
|
||||
if (parsed.ExpiresIn > 0)
|
||||
{
|
||||
expiresAt = DateTimeOffset.UtcNow.AddSeconds(parsed.ExpiresIn);
|
||||
}
|
||||
|
||||
parsed.AccessToken = null;
|
||||
|
||||
log.Information(component, "Infisical authentication was successful.");
|
||||
return new InfisicalAuthenticationResult
|
||||
{
|
||||
AccessToken = accessToken,
|
||||
TokenType = string.IsNullOrEmpty(parsed.TokenType) ? "Bearer" : parsed.TokenType,
|
||||
ExpiresAtUtc = expiresAt
|
||||
};
|
||||
}
|
||||
finally
|
||||
{
|
||||
response.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class IdentityLoginResponse
|
||||
{
|
||||
[Newtonsoft.Json.JsonProperty("accessToken")]
|
||||
public string AccessToken { get; set; }
|
||||
|
||||
[Newtonsoft.Json.JsonProperty("expiresIn")]
|
||||
public int ExpiresIn { get; set; }
|
||||
|
||||
[Newtonsoft.Json.JsonProperty("tokenType")]
|
||||
public string TokenType { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,5 +10,10 @@ namespace PSInfisicalAPI.Authentication
|
||||
public string ClientId { get; set; }
|
||||
public SecureString ClientSecret { get; set; }
|
||||
public SecureString PreSuppliedAccessToken { get; set; }
|
||||
|
||||
public string IdentityId { get; set; }
|
||||
public SecureString Jwt { get; set; }
|
||||
public string Username { get; set; }
|
||||
public SecureString Password { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
using System.Collections.Generic;
|
||||
using PSInfisicalAPI.Endpoints;
|
||||
using PSInfisicalAPI.Errors;
|
||||
using PSInfisicalAPI.Http;
|
||||
using PSInfisicalAPI.Logging;
|
||||
using PSInfisicalAPI.Security;
|
||||
|
||||
namespace PSInfisicalAPI.Authentication
|
||||
{
|
||||
public sealed class JwtAuthProvider : IInfisicalAuthProvider
|
||||
{
|
||||
private const string Component = "JwtAuthProvider";
|
||||
|
||||
public string Name { get { return "JwtAuth"; } }
|
||||
|
||||
public InfisicalAuthenticationResult Authenticate(InfisicalAuthenticationRequest request, IInfisicalHttpClient httpClient, IInfisicalLogger logger)
|
||||
{
|
||||
if (request == null || string.IsNullOrEmpty(request.IdentityId))
|
||||
{
|
||||
throw new InfisicalAuthenticationException("IdentityId is required for JWT Auth.");
|
||||
}
|
||||
|
||||
if (request.Jwt == null || request.Jwt.Length == 0)
|
||||
{
|
||||
throw new InfisicalAuthenticationException("Jwt is required for JWT Auth.");
|
||||
}
|
||||
|
||||
return IdentityLoginExecutor.Execute(InfisicalEndpointNames.JwtAuthLogin, Component, request, httpClient, logger, serializer =>
|
||||
{
|
||||
return SecureStringUtility.UsePlainText(request.Jwt, plainJwt =>
|
||||
{
|
||||
Dictionary<string, string> bodyObject = new Dictionary<string, string>
|
||||
{
|
||||
{ "identityId", request.IdentityId },
|
||||
{ "jwt", plainJwt ?? string.Empty }
|
||||
};
|
||||
|
||||
return serializer.Serialize(bodyObject);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using System.Collections.Generic;
|
||||
using PSInfisicalAPI.Endpoints;
|
||||
using PSInfisicalAPI.Errors;
|
||||
using PSInfisicalAPI.Http;
|
||||
using PSInfisicalAPI.Logging;
|
||||
using PSInfisicalAPI.Security;
|
||||
|
||||
namespace PSInfisicalAPI.Authentication
|
||||
{
|
||||
public sealed class LdapAuthProvider : IInfisicalAuthProvider
|
||||
{
|
||||
private const string Component = "LdapAuthProvider";
|
||||
|
||||
public string Name { get { return "LdapAuth"; } }
|
||||
|
||||
public InfisicalAuthenticationResult Authenticate(InfisicalAuthenticationRequest request, IInfisicalHttpClient httpClient, IInfisicalLogger logger)
|
||||
{
|
||||
if (request == null || string.IsNullOrEmpty(request.Username))
|
||||
{
|
||||
throw new InfisicalAuthenticationException("Username is required for LDAP Auth.");
|
||||
}
|
||||
|
||||
if (request.Password == null || request.Password.Length == 0)
|
||||
{
|
||||
throw new InfisicalAuthenticationException("Password is required for LDAP Auth.");
|
||||
}
|
||||
|
||||
return IdentityLoginExecutor.Execute(InfisicalEndpointNames.LdapAuthLogin, Component, request, httpClient, logger, serializer =>
|
||||
{
|
||||
return SecureStringUtility.UsePlainText(request.Password, plainPassword =>
|
||||
{
|
||||
Dictionary<string, string> bodyObject = new Dictionary<string, string>
|
||||
{
|
||||
{ "username", request.Username },
|
||||
{ "password", plainPassword ?? string.Empty }
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(request.IdentityId))
|
||||
{
|
||||
bodyObject["identityId"] = request.IdentityId;
|
||||
}
|
||||
|
||||
return serializer.Serialize(bodyObject);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System.Collections.Generic;
|
||||
using PSInfisicalAPI.Endpoints;
|
||||
using PSInfisicalAPI.Errors;
|
||||
using PSInfisicalAPI.Http;
|
||||
using PSInfisicalAPI.Logging;
|
||||
using PSInfisicalAPI.Security;
|
||||
|
||||
namespace PSInfisicalAPI.Authentication
|
||||
{
|
||||
public sealed class OidcAuthProvider : IInfisicalAuthProvider
|
||||
{
|
||||
private const string Component = "OidcAuthProvider";
|
||||
|
||||
public string Name { get { return "OidcAuth"; } }
|
||||
|
||||
public InfisicalAuthenticationResult Authenticate(InfisicalAuthenticationRequest request, IInfisicalHttpClient httpClient, IInfisicalLogger logger)
|
||||
{
|
||||
if (request == null || string.IsNullOrEmpty(request.IdentityId))
|
||||
{
|
||||
throw new InfisicalAuthenticationException("IdentityId is required for OIDC Auth.");
|
||||
}
|
||||
|
||||
if (request.Jwt == null || request.Jwt.Length == 0)
|
||||
{
|
||||
throw new InfisicalAuthenticationException("Jwt is required for OIDC Auth.");
|
||||
}
|
||||
|
||||
return IdentityLoginExecutor.Execute(InfisicalEndpointNames.OidcAuthLogin, Component, request, httpClient, logger, serializer =>
|
||||
{
|
||||
return SecureStringUtility.UsePlainText(request.Jwt, plainJwt =>
|
||||
{
|
||||
Dictionary<string, string> bodyObject = new Dictionary<string, string>
|
||||
{
|
||||
{ "identityId", request.IdentityId },
|
||||
{ "jwt", plainJwt ?? string.Empty }
|
||||
};
|
||||
|
||||
return serializer.Serialize(bodyObject);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Management.Automation;
|
||||
using PSInfisicalAPI.Security;
|
||||
|
||||
namespace PSInfisicalAPI.Cmdlets
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
|
||||
public sealed class BulkSecretsTransformationAttribute : ArgumentTransformationAttribute
|
||||
{
|
||||
public override object Transform(EngineIntrinsics engineIntrinsics, object inputData)
|
||||
{
|
||||
if (inputData == null) { return null; }
|
||||
|
||||
object unwrapped = Unwrap(inputData);
|
||||
|
||||
if (unwrapped is IDictionary<string, string>[] strongArray) { return strongArray; }
|
||||
|
||||
if (unwrapped is IDictionary singleDict)
|
||||
{
|
||||
return new IDictionary<string, string>[] { Convert(singleDict) };
|
||||
}
|
||||
|
||||
if (unwrapped is IEnumerable enumerable && !(unwrapped is string))
|
||||
{
|
||||
List<IDictionary<string, string>> result = new List<IDictionary<string, string>>();
|
||||
foreach (object element in enumerable)
|
||||
{
|
||||
if (element == null) { continue; }
|
||||
object e = Unwrap(element);
|
||||
if (e is IDictionary dict)
|
||||
{
|
||||
result.Add(Convert(dict));
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new ArgumentTransformationMetadataException(
|
||||
"Each element of -Secrets must be a dictionary (Hashtable, OrderedDictionary, Dictionary<string,string>, etc.).");
|
||||
}
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
throw new ArgumentTransformationMetadataException(
|
||||
"-Secrets must be a dictionary or an array of dictionaries.");
|
||||
}
|
||||
|
||||
private static object Unwrap(object value)
|
||||
{
|
||||
PSObject pso = value as PSObject;
|
||||
return pso != null ? pso.BaseObject : value;
|
||||
}
|
||||
|
||||
private static IDictionary<string, string> Convert(IDictionary source)
|
||||
{
|
||||
Dictionary<string, string> dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (DictionaryEntry entry in source)
|
||||
{
|
||||
if (entry.Key == null) { continue; }
|
||||
string key = entry.Key.ToString();
|
||||
dict[key] = Stringify(entry.Value);
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
private static string Stringify(object value)
|
||||
{
|
||||
object v = Unwrap(value);
|
||||
if (v == null) { return null; }
|
||||
if (v is string s) { return s; }
|
||||
if (v is bool b) { return b ? "true" : "false"; }
|
||||
if (v is System.Security.SecureString secure)
|
||||
{
|
||||
return SecureStringUtility.UsePlainText(secure, plain => plain);
|
||||
}
|
||||
|
||||
if (v is IEnumerable enumerable)
|
||||
{
|
||||
List<string> parts = new List<string>();
|
||||
foreach (object item in enumerable)
|
||||
{
|
||||
if (item == null) { continue; }
|
||||
parts.Add(Unwrap(item).ToString());
|
||||
}
|
||||
|
||||
return string.Join(",", parts);
|
||||
}
|
||||
|
||||
return v.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,11 @@ namespace PSInfisicalAPI.Cmdlets
|
||||
{
|
||||
private const string ParameterSetUniversalAuth = "UniversalAuth";
|
||||
private const string ParameterSetToken = "Token";
|
||||
private const string ParameterSetJwt = "JwtAuth";
|
||||
private const string ParameterSetOidc = "OidcAuth";
|
||||
private const string ParameterSetLdap = "LdapAuth";
|
||||
private const string ParameterSetAzure = "AzureAuth";
|
||||
private const string ParameterSetGcpIam = "GcpIamAuth";
|
||||
private const string Component = "ConnectInfisicalCmdlet";
|
||||
|
||||
[Parameter]
|
||||
@@ -38,6 +43,25 @@ namespace PSInfisicalAPI.Cmdlets
|
||||
[Parameter(ParameterSetName = ParameterSetToken)]
|
||||
public SecureString AccessToken { get; set; }
|
||||
|
||||
[Parameter(Mandatory = true, ParameterSetName = ParameterSetJwt)]
|
||||
[Parameter(Mandatory = true, ParameterSetName = ParameterSetOidc)]
|
||||
[Parameter(Mandatory = true, ParameterSetName = ParameterSetAzure)]
|
||||
[Parameter(Mandatory = true, ParameterSetName = ParameterSetGcpIam)]
|
||||
[Parameter(ParameterSetName = ParameterSetLdap)]
|
||||
public string IdentityId { get; set; }
|
||||
|
||||
[Parameter(Mandatory = true, ParameterSetName = ParameterSetJwt)]
|
||||
[Parameter(Mandatory = true, ParameterSetName = ParameterSetOidc)]
|
||||
[Parameter(Mandatory = true, ParameterSetName = ParameterSetAzure)]
|
||||
[Parameter(Mandatory = true, ParameterSetName = ParameterSetGcpIam)]
|
||||
public SecureString Jwt { get; set; }
|
||||
|
||||
[Parameter(Mandatory = true, ParameterSetName = ParameterSetLdap)]
|
||||
public string Username { get; set; }
|
||||
|
||||
[Parameter(Mandatory = true, ParameterSetName = ParameterSetLdap)]
|
||||
public SecureString Password { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string SecretPath { get; set; } = "/";
|
||||
|
||||
@@ -58,28 +82,91 @@ namespace PSInfisicalAPI.Cmdlets
|
||||
InfisicalAuthenticationRequest request;
|
||||
InfisicalAuthType authType;
|
||||
|
||||
if (string.Equals(ParameterSetName, ParameterSetToken, StringComparison.Ordinal))
|
||||
switch (ParameterSetName)
|
||||
{
|
||||
provider = new TokenAuthProvider();
|
||||
authType = InfisicalAuthType.Token;
|
||||
request = new InfisicalAuthenticationRequest
|
||||
{
|
||||
BaseUri = BaseUri,
|
||||
ApiVersion = ApiVersion,
|
||||
PreSuppliedAccessToken = AccessToken
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
provider = new UniversalAuthProvider();
|
||||
authType = InfisicalAuthType.UniversalAuth;
|
||||
request = new InfisicalAuthenticationRequest
|
||||
{
|
||||
BaseUri = BaseUri,
|
||||
ApiVersion = ApiVersion,
|
||||
ClientId = ClientId,
|
||||
ClientSecret = ClientSecret
|
||||
};
|
||||
case ParameterSetToken:
|
||||
provider = new TokenAuthProvider();
|
||||
authType = InfisicalAuthType.Token;
|
||||
request = new InfisicalAuthenticationRequest
|
||||
{
|
||||
BaseUri = BaseUri,
|
||||
ApiVersion = ApiVersion,
|
||||
PreSuppliedAccessToken = AccessToken
|
||||
};
|
||||
break;
|
||||
|
||||
case ParameterSetJwt:
|
||||
provider = new JwtAuthProvider();
|
||||
authType = InfisicalAuthType.Jwt;
|
||||
request = new InfisicalAuthenticationRequest
|
||||
{
|
||||
BaseUri = BaseUri,
|
||||
ApiVersion = ApiVersion,
|
||||
IdentityId = IdentityId,
|
||||
Jwt = Jwt
|
||||
};
|
||||
break;
|
||||
|
||||
case ParameterSetOidc:
|
||||
provider = new OidcAuthProvider();
|
||||
authType = InfisicalAuthType.Oidc;
|
||||
request = new InfisicalAuthenticationRequest
|
||||
{
|
||||
BaseUri = BaseUri,
|
||||
ApiVersion = ApiVersion,
|
||||
IdentityId = IdentityId,
|
||||
Jwt = Jwt
|
||||
};
|
||||
break;
|
||||
|
||||
case ParameterSetLdap:
|
||||
provider = new LdapAuthProvider();
|
||||
authType = InfisicalAuthType.Ldap;
|
||||
request = new InfisicalAuthenticationRequest
|
||||
{
|
||||
BaseUri = BaseUri,
|
||||
ApiVersion = ApiVersion,
|
||||
IdentityId = IdentityId,
|
||||
Username = Username,
|
||||
Password = Password
|
||||
};
|
||||
break;
|
||||
|
||||
case ParameterSetAzure:
|
||||
provider = new AzureAuthProvider();
|
||||
authType = InfisicalAuthType.Azure;
|
||||
request = new InfisicalAuthenticationRequest
|
||||
{
|
||||
BaseUri = BaseUri,
|
||||
ApiVersion = ApiVersion,
|
||||
IdentityId = IdentityId,
|
||||
Jwt = Jwt
|
||||
};
|
||||
break;
|
||||
|
||||
case ParameterSetGcpIam:
|
||||
provider = new GcpIamAuthProvider();
|
||||
authType = InfisicalAuthType.GcpIam;
|
||||
request = new InfisicalAuthenticationRequest
|
||||
{
|
||||
BaseUri = BaseUri,
|
||||
ApiVersion = ApiVersion,
|
||||
IdentityId = IdentityId,
|
||||
Jwt = Jwt
|
||||
};
|
||||
break;
|
||||
|
||||
default:
|
||||
provider = new UniversalAuthProvider();
|
||||
authType = InfisicalAuthType.UniversalAuth;
|
||||
request = new InfisicalAuthenticationRequest
|
||||
{
|
||||
BaseUri = BaseUri,
|
||||
ApiVersion = ApiVersion,
|
||||
ClientId = ClientId,
|
||||
ClientSecret = ClientSecret
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
InfisicalAuthenticationResult authResult = provider.Authenticate(request, HttpClient, Logger);
|
||||
@@ -123,6 +210,7 @@ namespace PSInfisicalAPI.Cmdlets
|
||||
private void ResolveMissingParametersFromEnvironment()
|
||||
{
|
||||
bool tokenSet = string.Equals(ParameterSetName, ParameterSetToken, StringComparison.Ordinal);
|
||||
bool universalSet = string.Equals(ParameterSetName, ParameterSetUniversalAuth, StringComparison.Ordinal);
|
||||
|
||||
bool needsScan =
|
||||
BaseUri == null ||
|
||||
@@ -130,8 +218,8 @@ namespace PSInfisicalAPI.Cmdlets
|
||||
string.IsNullOrWhiteSpace(ProjectId) ||
|
||||
string.IsNullOrWhiteSpace(Environment) ||
|
||||
(tokenSet && (AccessToken == null || AccessToken.Length == 0)) ||
|
||||
(!tokenSet && string.IsNullOrWhiteSpace(ClientId)) ||
|
||||
(!tokenSet && (ClientSecret == null || ClientSecret.Length == 0));
|
||||
(universalSet && string.IsNullOrWhiteSpace(ClientId)) ||
|
||||
(universalSet && (ClientSecret == null || ClientSecret.Length == 0));
|
||||
|
||||
if (!needsScan)
|
||||
{
|
||||
@@ -161,7 +249,7 @@ namespace PSInfisicalAPI.Cmdlets
|
||||
{
|
||||
AccessToken = InfisicalEnvironmentResolver.ResolveSecureString("AccessToken", InfisicalEnvironmentResolver.AccessTokenPatterns, AccessToken, Logger);
|
||||
}
|
||||
else
|
||||
else if (universalSet)
|
||||
{
|
||||
ClientId = InfisicalEnvironmentResolver.ResolveString("ClientId", InfisicalEnvironmentResolver.ClientIdPatterns, ClientId, Logger);
|
||||
ClientSecret = InfisicalEnvironmentResolver.ResolveSecureString("ClientSecret", InfisicalEnvironmentResolver.ClientSecretPatterns, ClientSecret, Logger);
|
||||
@@ -199,7 +287,7 @@ namespace PSInfisicalAPI.Cmdlets
|
||||
{
|
||||
if (AccessToken == null || AccessToken.Length == 0) { missing.Add("AccessToken"); }
|
||||
}
|
||||
else
|
||||
else if (string.Equals(ParameterSetName, ParameterSetUniversalAuth, StringComparison.Ordinal))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(ClientId)) { missing.Add("ClientId"); }
|
||||
if (ClientSecret == null || ClientSecret.Length == 0) { missing.Add("ClientSecret"); }
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using PSInfisicalAPI.Connections;
|
||||
using PSInfisicalAPI.Environments;
|
||||
using PSInfisicalAPI.Models;
|
||||
|
||||
namespace PSInfisicalAPI.Cmdlets
|
||||
{
|
||||
[Cmdlet(VerbsCommon.Get, "InfisicalEnvironment")]
|
||||
[OutputType(typeof(InfisicalEnvironment))]
|
||||
public sealed class GetInfisicalEnvironmentCmdlet : InfisicalCmdletBase
|
||||
{
|
||||
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0)]
|
||||
[Alias("Slug", "Id", "Environment")]
|
||||
public string EnvironmentSlugOrId { get; set; }
|
||||
|
||||
[Parameter] public string ProjectId { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
try
|
||||
{
|
||||
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
|
||||
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
|
||||
InfisicalEnvironmentClient client = new InfisicalEnvironmentClient(HttpClient, Logger);
|
||||
InfisicalEnvironment env = client.Retrieve(connection, resolvedProjectId, EnvironmentSlugOrId);
|
||||
if (env != null)
|
||||
{
|
||||
WriteObject(env);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
ThrowTerminatingForException("GetInfisicalEnvironmentCmdlet", "RetrieveEnvironment", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using PSInfisicalAPI.Connections;
|
||||
using PSInfisicalAPI.Environments;
|
||||
using PSInfisicalAPI.Models;
|
||||
|
||||
namespace PSInfisicalAPI.Cmdlets
|
||||
{
|
||||
[Cmdlet(VerbsCommon.Get, "InfisicalEnvironments")]
|
||||
[OutputType(typeof(InfisicalEnvironment))]
|
||||
public sealed class GetInfisicalEnvironmentsCmdlet : InfisicalCmdletBase
|
||||
{
|
||||
[Parameter] public string ProjectId { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
try
|
||||
{
|
||||
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
|
||||
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
|
||||
InfisicalEnvironmentClient client = new InfisicalEnvironmentClient(HttpClient, Logger);
|
||||
InfisicalEnvironment[] envs = client.List(connection, resolvedProjectId);
|
||||
foreach (InfisicalEnvironment env in envs)
|
||||
{
|
||||
WriteObject(env);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
ThrowTerminatingForException("GetInfisicalEnvironmentsCmdlet", "ListEnvironments", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using PSInfisicalAPI.Connections;
|
||||
using PSInfisicalAPI.Folders;
|
||||
using PSInfisicalAPI.Models;
|
||||
|
||||
namespace PSInfisicalAPI.Cmdlets
|
||||
{
|
||||
[Cmdlet(VerbsCommon.Get, "InfisicalFolder")]
|
||||
[OutputType(typeof(InfisicalFolder))]
|
||||
public sealed class GetInfisicalFolderCmdlet : InfisicalCmdletBase
|
||||
{
|
||||
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0)]
|
||||
[Alias("Name", "Id")]
|
||||
public string FolderNameOrId { get; set; }
|
||||
|
||||
[Parameter] public string ProjectId { get; set; }
|
||||
[Parameter] public string Environment { get; set; }
|
||||
[Parameter] public string Path { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
try
|
||||
{
|
||||
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
|
||||
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
|
||||
string resolvedEnvironment = ResolveEnvironment(connection, Environment);
|
||||
string resolvedPath = ResolveSecretPath(connection, Path);
|
||||
InfisicalFolderClient client = new InfisicalFolderClient(HttpClient, Logger);
|
||||
InfisicalFolder folder = client.Retrieve(connection, resolvedProjectId, resolvedEnvironment, resolvedPath, FolderNameOrId);
|
||||
if (folder != null)
|
||||
{
|
||||
WriteObject(folder);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
ThrowTerminatingForException("GetInfisicalFolderCmdlet", "RetrieveFolder", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using PSInfisicalAPI.Connections;
|
||||
using PSInfisicalAPI.Folders;
|
||||
using PSInfisicalAPI.Models;
|
||||
|
||||
namespace PSInfisicalAPI.Cmdlets
|
||||
{
|
||||
[Cmdlet(VerbsCommon.Get, "InfisicalFolders")]
|
||||
[OutputType(typeof(InfisicalFolder))]
|
||||
public sealed class GetInfisicalFoldersCmdlet : InfisicalCmdletBase
|
||||
{
|
||||
[Parameter] public string ProjectId { get; set; }
|
||||
[Parameter] public string Environment { get; set; }
|
||||
[Parameter] public string Path { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
try
|
||||
{
|
||||
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
|
||||
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
|
||||
string resolvedEnvironment = ResolveEnvironment(connection, Environment);
|
||||
string resolvedPath = ResolveSecretPath(connection, Path);
|
||||
InfisicalFolderClient client = new InfisicalFolderClient(HttpClient, Logger);
|
||||
InfisicalFolder[] folders = client.List(connection, resolvedProjectId, resolvedEnvironment, resolvedPath);
|
||||
foreach (InfisicalFolder folder in folders)
|
||||
{
|
||||
WriteObject(folder);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
ThrowTerminatingForException("GetInfisicalFoldersCmdlet", "ListFolders", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using PSInfisicalAPI.Connections;
|
||||
using PSInfisicalAPI.Models;
|
||||
using PSInfisicalAPI.Projects;
|
||||
|
||||
namespace PSInfisicalAPI.Cmdlets
|
||||
{
|
||||
[Cmdlet(VerbsCommon.Get, "InfisicalProject")]
|
||||
[OutputType(typeof(InfisicalProject))]
|
||||
public sealed class GetInfisicalProjectCmdlet : InfisicalCmdletBase
|
||||
{
|
||||
[Parameter(ValueFromPipelineByPropertyName = true, Position = 0)]
|
||||
[Alias("Id")]
|
||||
public string ProjectId { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
try
|
||||
{
|
||||
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
|
||||
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
|
||||
InfisicalProjectClient client = new InfisicalProjectClient(HttpClient, Logger);
|
||||
InfisicalProject project = client.Retrieve(connection, resolvedProjectId);
|
||||
if (project != null)
|
||||
{
|
||||
WriteObject(project);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
ThrowTerminatingForException("GetInfisicalProjectCmdlet", "RetrieveProject", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using PSInfisicalAPI.Connections;
|
||||
using PSInfisicalAPI.Models;
|
||||
using PSInfisicalAPI.Projects;
|
||||
|
||||
namespace PSInfisicalAPI.Cmdlets
|
||||
{
|
||||
[Cmdlet(VerbsCommon.Get, "InfisicalProjects")]
|
||||
[OutputType(typeof(InfisicalProject))]
|
||||
public sealed class GetInfisicalProjectsCmdlet : InfisicalCmdletBase
|
||||
{
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
try
|
||||
{
|
||||
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
|
||||
InfisicalProjectClient client = new InfisicalProjectClient(HttpClient, Logger);
|
||||
InfisicalProject[] projects = client.List(connection);
|
||||
|
||||
foreach (InfisicalProject project in projects)
|
||||
{
|
||||
WriteObject(project);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
ThrowTerminatingForException("GetInfisicalProjectsCmdlet", "ListProjects", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,10 +32,10 @@ namespace PSInfisicalAPI.Cmdlets
|
||||
InfisicalRetrieveSecretQuery query = new InfisicalRetrieveSecretQuery
|
||||
{
|
||||
SecretName = SecretName,
|
||||
ProjectId = ProjectId,
|
||||
Environment = Environment,
|
||||
SecretPath = SecretPath,
|
||||
ApiVersion = ApiVersion,
|
||||
ProjectId = ResolveProjectId(connection, ProjectId),
|
||||
Environment = ResolveEnvironment(connection, Environment),
|
||||
SecretPath = ResolveSecretPath(connection, SecretPath),
|
||||
ApiVersion = ResolveApiVersion(connection, ApiVersion),
|
||||
Version = Version,
|
||||
Type = Type.ToString(),
|
||||
ViewSecretValue = ViewSecretValue.IsPresent,
|
||||
|
||||
@@ -32,10 +32,10 @@ namespace PSInfisicalAPI.Cmdlets
|
||||
|
||||
InfisicalListSecretsQuery query = new InfisicalListSecretsQuery
|
||||
{
|
||||
ProjectId = ProjectId,
|
||||
Environment = Environment,
|
||||
SecretPath = SecretPath,
|
||||
ApiVersion = ApiVersion,
|
||||
ProjectId = ResolveProjectId(connection, ProjectId),
|
||||
Environment = ResolveEnvironment(connection, Environment),
|
||||
SecretPath = ResolveSecretPath(connection, SecretPath),
|
||||
ApiVersion = ResolveApiVersion(connection, ApiVersion),
|
||||
Recursive = Recursive.IsPresent,
|
||||
IncludeImports = IncludeImports.IsPresent,
|
||||
IncludePersonalOverrides = IncludePersonalOverrides.IsPresent,
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using PSInfisicalAPI.Connections;
|
||||
using PSInfisicalAPI.Models;
|
||||
using PSInfisicalAPI.Tags;
|
||||
|
||||
namespace PSInfisicalAPI.Cmdlets
|
||||
{
|
||||
[Cmdlet(VerbsCommon.Get, "InfisicalTag")]
|
||||
[OutputType(typeof(InfisicalTag))]
|
||||
public sealed class GetInfisicalTagCmdlet : InfisicalCmdletBase
|
||||
{
|
||||
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0)]
|
||||
[Alias("Slug", "Id")]
|
||||
public string TagSlugOrId { get; set; }
|
||||
|
||||
[Parameter] public string ProjectId { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
try
|
||||
{
|
||||
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
|
||||
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
|
||||
InfisicalTagClient client = new InfisicalTagClient(HttpClient, Logger);
|
||||
InfisicalTag tag = client.Retrieve(connection, resolvedProjectId, TagSlugOrId);
|
||||
if (tag != null)
|
||||
{
|
||||
WriteObject(tag);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
ThrowTerminatingForException("GetInfisicalTagCmdlet", "RetrieveTag", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using PSInfisicalAPI.Connections;
|
||||
using PSInfisicalAPI.Models;
|
||||
using PSInfisicalAPI.Tags;
|
||||
|
||||
namespace PSInfisicalAPI.Cmdlets
|
||||
{
|
||||
[Cmdlet(VerbsCommon.Get, "InfisicalTags")]
|
||||
[OutputType(typeof(InfisicalTag))]
|
||||
public sealed class GetInfisicalTagsCmdlet : InfisicalCmdletBase
|
||||
{
|
||||
[Parameter] public string ProjectId { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
try
|
||||
{
|
||||
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
|
||||
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
|
||||
InfisicalTagClient client = new InfisicalTagClient(HttpClient, Logger);
|
||||
InfisicalTag[] tags = client.List(connection, resolvedProjectId);
|
||||
foreach (InfisicalTag tag in tags)
|
||||
{
|
||||
WriteObject(tag);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
ThrowTerminatingForException("GetInfisicalTagsCmdlet", "ListTags", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using PSInfisicalAPI.Connections;
|
||||
using PSInfisicalAPI.Errors;
|
||||
using PSInfisicalAPI.Http;
|
||||
using PSInfisicalAPI.Logging;
|
||||
@@ -44,5 +45,43 @@ namespace PSInfisicalAPI.Cmdlets
|
||||
ErrorRecord record = InfisicalErrorHandler.ToErrorRecord(exception, details);
|
||||
ThrowTerminatingError(record);
|
||||
}
|
||||
|
||||
protected string ResolveProjectId(InfisicalConnection connection, string explicitValue)
|
||||
{
|
||||
return ResolveValue("ProjectId", explicitValue, connection != null ? connection.ProjectId : null, null);
|
||||
}
|
||||
|
||||
protected string ResolveEnvironment(InfisicalConnection connection, string explicitValue)
|
||||
{
|
||||
return ResolveValue("Environment", explicitValue, connection != null ? connection.Environment : null, null);
|
||||
}
|
||||
|
||||
protected string ResolveSecretPath(InfisicalConnection connection, string explicitValue)
|
||||
{
|
||||
return ResolveValue("SecretPath", explicitValue, connection != null ? connection.DefaultSecretPath : null, "/");
|
||||
}
|
||||
|
||||
protected string ResolveApiVersion(InfisicalConnection connection, string explicitValue)
|
||||
{
|
||||
string fromConnection = connection != null ? (!string.IsNullOrEmpty(connection.PinnedApiVersion) ? connection.PinnedApiVersion : connection.ApiVersion) : null;
|
||||
return ResolveValue("ApiVersion", explicitValue, fromConnection, null);
|
||||
}
|
||||
|
||||
protected string ResolveOrganizationId(InfisicalConnection connection, string explicitValue)
|
||||
{
|
||||
return ResolveValue("OrganizationId", explicitValue, connection != null ? connection.OrganizationId : null, null);
|
||||
}
|
||||
|
||||
private string ResolveValue(string parameterName, string explicitValue, string inheritedValue, string defaultValue)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(explicitValue)) { return explicitValue; }
|
||||
if (!string.IsNullOrEmpty(inheritedValue))
|
||||
{
|
||||
Logger.Verbose(GetType().Name, string.Concat("Inherited ", parameterName, " '", inheritedValue, "' from connection."));
|
||||
return inheritedValue;
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using PSInfisicalAPI.Connections;
|
||||
using PSInfisicalAPI.Environments;
|
||||
using PSInfisicalAPI.Models;
|
||||
|
||||
namespace PSInfisicalAPI.Cmdlets
|
||||
{
|
||||
[Cmdlet(VerbsCommon.New, "InfisicalEnvironment", SupportsShouldProcess = true)]
|
||||
[OutputType(typeof(InfisicalEnvironment))]
|
||||
public sealed class NewInfisicalEnvironmentCmdlet : InfisicalCmdletBase
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0)] public string Name { get; set; }
|
||||
[Parameter(Mandatory = true, Position = 1)] public string Slug { get; set; }
|
||||
[Parameter] public string ProjectId { get; set; }
|
||||
[Parameter] public int? Position { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!ShouldProcess(Slug, "Create Infisical environment"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
|
||||
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
|
||||
InfisicalEnvironmentClient client = new InfisicalEnvironmentClient(HttpClient, Logger);
|
||||
InfisicalEnvironment env = client.Create(connection, resolvedProjectId, Name, Slug, Position);
|
||||
if (env != null)
|
||||
{
|
||||
WriteObject(env);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
ThrowTerminatingForException("NewInfisicalEnvironmentCmdlet", "CreateEnvironment", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using PSInfisicalAPI.Connections;
|
||||
using PSInfisicalAPI.Folders;
|
||||
using PSInfisicalAPI.Models;
|
||||
|
||||
namespace PSInfisicalAPI.Cmdlets
|
||||
{
|
||||
[Cmdlet(VerbsCommon.New, "InfisicalFolder", SupportsShouldProcess = true)]
|
||||
[OutputType(typeof(InfisicalFolder))]
|
||||
public sealed class NewInfisicalFolderCmdlet : InfisicalCmdletBase
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0)] public string Name { get; set; }
|
||||
[Parameter] public string ProjectId { get; set; }
|
||||
[Parameter] public string Environment { get; set; }
|
||||
[Parameter] public string Path { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!ShouldProcess(Name, "Create Infisical folder"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
|
||||
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
|
||||
string resolvedEnvironment = ResolveEnvironment(connection, Environment);
|
||||
string resolvedPath = ResolveSecretPath(connection, Path);
|
||||
InfisicalFolderClient client = new InfisicalFolderClient(HttpClient, Logger);
|
||||
InfisicalFolder folder = client.Create(connection, resolvedProjectId, resolvedEnvironment, Name, resolvedPath);
|
||||
if (folder != null)
|
||||
{
|
||||
WriteObject(folder);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
ThrowTerminatingForException("NewInfisicalFolderCmdlet", "CreateFolder", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using PSInfisicalAPI.Connections;
|
||||
using PSInfisicalAPI.Models;
|
||||
using PSInfisicalAPI.Projects;
|
||||
|
||||
namespace PSInfisicalAPI.Cmdlets
|
||||
{
|
||||
[Cmdlet(VerbsCommon.New, "InfisicalProject", SupportsShouldProcess = true)]
|
||||
[OutputType(typeof(InfisicalProject))]
|
||||
public sealed class NewInfisicalProjectCmdlet : InfisicalCmdletBase
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0)]
|
||||
[Alias("Name")]
|
||||
public string ProjectName { get; set; }
|
||||
|
||||
[Parameter] public string Slug { get; set; }
|
||||
[Parameter] public string Description { get; set; }
|
||||
[Parameter] public string Type { get; set; }
|
||||
[Parameter] public string OrganizationId { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!ShouldProcess(ProjectName, "Create Infisical project"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
|
||||
string resolvedOrgId = !string.IsNullOrEmpty(OrganizationId) ? OrganizationId : connection.OrganizationId;
|
||||
|
||||
InfisicalProjectClient client = new InfisicalProjectClient(HttpClient, Logger);
|
||||
InfisicalProject project = client.Create(connection, ProjectName, Slug, Description, Type, resolvedOrgId);
|
||||
if (project != null)
|
||||
{
|
||||
WriteObject(project);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
ThrowTerminatingForException("NewInfisicalProjectCmdlet", "CreateProject", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Management.Automation;
|
||||
using System.Security;
|
||||
using PSInfisicalAPI.Connections;
|
||||
using PSInfisicalAPI.Models;
|
||||
using PSInfisicalAPI.Secrets;
|
||||
using PSInfisicalAPI.Security;
|
||||
|
||||
namespace PSInfisicalAPI.Cmdlets
|
||||
{
|
||||
[Cmdlet(VerbsCommon.New, "InfisicalSecret", SupportsShouldProcess = true, DefaultParameterSetName = "PlainText")]
|
||||
[OutputType(typeof(InfisicalSecret))]
|
||||
public sealed class NewInfisicalSecretCmdlet : InfisicalCmdletBase
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "PlainText")]
|
||||
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "SecureString")]
|
||||
public string SecretName { get; set; }
|
||||
|
||||
[Parameter(Mandatory = true, Position = 1, ParameterSetName = "PlainText")]
|
||||
public string SecretValue { get; set; }
|
||||
|
||||
[Parameter(Mandatory = true, Position = 1, ParameterSetName = "SecureString")]
|
||||
public SecureString SecureSecretValue { get; set; }
|
||||
|
||||
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "Bulk", ValueFromPipeline = true)]
|
||||
[BulkSecretsTransformation]
|
||||
public IDictionary<string, string>[] Secrets { get; set; }
|
||||
|
||||
[Parameter] public string SecretComment { get; set; }
|
||||
[Parameter] public string ProjectId { get; set; }
|
||||
[Parameter] public string Environment { get; set; }
|
||||
[Parameter] public string SecretPath { get; set; }
|
||||
[Parameter] public string ApiVersion { get; set; }
|
||||
[Parameter] public InfisicalSecretType Type { get; set; } = InfisicalSecretType.Shared;
|
||||
[Parameter] public SwitchParameter SkipMultilineEncoding { get; set; }
|
||||
[Parameter] public string[] TagIds { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
try
|
||||
{
|
||||
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
|
||||
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
|
||||
string resolvedEnvironment = ResolveEnvironment(connection, Environment);
|
||||
string resolvedSecretPath = ResolveSecretPath(connection, SecretPath);
|
||||
string resolvedApiVersion = ResolveApiVersion(connection, ApiVersion);
|
||||
|
||||
if (string.Equals(ParameterSetName, "Bulk", StringComparison.Ordinal))
|
||||
{
|
||||
if (Secrets == null || Secrets.Length == 0) { return; }
|
||||
string target = string.Concat(Secrets.Length, " secret(s)");
|
||||
if (!ShouldProcess(target, "Bulk-create Infisical secrets")) { return; }
|
||||
|
||||
InfisicalBulkCreateSecretsRequest bulk = new InfisicalBulkCreateSecretsRequest
|
||||
{
|
||||
ProjectId = resolvedProjectId,
|
||||
Environment = resolvedEnvironment,
|
||||
SecretPath = resolvedSecretPath,
|
||||
ApiVersion = resolvedApiVersion,
|
||||
Secrets = InfisicalBulkSecretConverter.ToCreateItems(Secrets)
|
||||
};
|
||||
|
||||
InfisicalSecretsClient bulkClient = new InfisicalSecretsClient(HttpClient, Logger);
|
||||
InfisicalSecret[] created = bulkClient.CreateBatch(connection, bulk);
|
||||
if (created != null)
|
||||
{
|
||||
foreach (InfisicalSecret secret in created) { WriteObject(secret); }
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ShouldProcess(SecretName, "Create Infisical secret")) { return; }
|
||||
|
||||
string plainValue = SecureSecretValue != null
|
||||
? SecureStringUtility.UsePlainText(SecureSecretValue, p => p)
|
||||
: SecretValue;
|
||||
|
||||
InfisicalCreateSecretRequest request = new InfisicalCreateSecretRequest
|
||||
{
|
||||
SecretName = SecretName,
|
||||
SecretValue = plainValue,
|
||||
SecretComment = SecretComment,
|
||||
ProjectId = resolvedProjectId,
|
||||
Environment = resolvedEnvironment,
|
||||
SecretPath = resolvedSecretPath,
|
||||
Type = Type.ToString(),
|
||||
ApiVersion = resolvedApiVersion,
|
||||
SkipMultilineEncoding = SkipMultilineEncoding.IsPresent ? (bool?)true : null,
|
||||
TagIds = TagIds
|
||||
};
|
||||
|
||||
InfisicalSecretsClient client = new InfisicalSecretsClient(HttpClient, Logger);
|
||||
InfisicalSecret single = client.Create(connection, request);
|
||||
if (single != null)
|
||||
{
|
||||
WriteObject(single);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
ThrowTerminatingForException("NewInfisicalSecretCmdlet", "CreateSecret", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using PSInfisicalAPI.Connections;
|
||||
using PSInfisicalAPI.Models;
|
||||
using PSInfisicalAPI.Tags;
|
||||
|
||||
namespace PSInfisicalAPI.Cmdlets
|
||||
{
|
||||
[Cmdlet(VerbsCommon.New, "InfisicalTag", SupportsShouldProcess = true)]
|
||||
[OutputType(typeof(InfisicalTag))]
|
||||
public sealed class NewInfisicalTagCmdlet : InfisicalCmdletBase
|
||||
{
|
||||
[Parameter(Mandatory = true, Position = 0)] public string Slug { get; set; }
|
||||
[Parameter] public string Name { get; set; }
|
||||
[Parameter] public string Color { get; set; }
|
||||
[Parameter] public string ProjectId { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!ShouldProcess(Slug, "Create Infisical tag"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
|
||||
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
|
||||
InfisicalTagClient client = new InfisicalTagClient(HttpClient, Logger);
|
||||
InfisicalTag tag = client.Create(connection, resolvedProjectId, Slug, Name, Color);
|
||||
if (tag != null)
|
||||
{
|
||||
WriteObject(tag);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
ThrowTerminatingForException("NewInfisicalTagCmdlet", "CreateTag", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using PSInfisicalAPI.Connections;
|
||||
using PSInfisicalAPI.Environments;
|
||||
|
||||
namespace PSInfisicalAPI.Cmdlets
|
||||
{
|
||||
[Cmdlet(VerbsCommon.Remove, "InfisicalEnvironment", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)]
|
||||
public sealed class RemoveInfisicalEnvironmentCmdlet : InfisicalCmdletBase
|
||||
{
|
||||
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0)]
|
||||
[Alias("Id")]
|
||||
public string EnvironmentId { get; set; }
|
||||
|
||||
[Parameter] public string ProjectId { get; set; }
|
||||
[Parameter] public SwitchParameter PassThru { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!ShouldProcess(EnvironmentId, "Remove Infisical environment"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
|
||||
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
|
||||
InfisicalEnvironmentClient client = new InfisicalEnvironmentClient(HttpClient, Logger);
|
||||
client.Delete(connection, resolvedProjectId, EnvironmentId);
|
||||
|
||||
if (PassThru.IsPresent)
|
||||
{
|
||||
WriteObject(EnvironmentId);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
ThrowTerminatingForException("RemoveInfisicalEnvironmentCmdlet", "DeleteEnvironment", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using PSInfisicalAPI.Connections;
|
||||
using PSInfisicalAPI.Folders;
|
||||
|
||||
namespace PSInfisicalAPI.Cmdlets
|
||||
{
|
||||
[Cmdlet(VerbsCommon.Remove, "InfisicalFolder", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)]
|
||||
public sealed class RemoveInfisicalFolderCmdlet : InfisicalCmdletBase
|
||||
{
|
||||
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0)]
|
||||
[Alias("Id")]
|
||||
public string FolderId { get; set; }
|
||||
|
||||
[Parameter] public string ProjectId { get; set; }
|
||||
[Parameter] public string Environment { get; set; }
|
||||
[Parameter] public string Path { get; set; }
|
||||
[Parameter] public SwitchParameter PassThru { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!ShouldProcess(FolderId, "Remove Infisical folder"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
|
||||
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
|
||||
string resolvedEnvironment = ResolveEnvironment(connection, Environment);
|
||||
string resolvedPath = ResolveSecretPath(connection, Path);
|
||||
InfisicalFolderClient client = new InfisicalFolderClient(HttpClient, Logger);
|
||||
client.Delete(connection, resolvedProjectId, resolvedEnvironment, FolderId, resolvedPath);
|
||||
|
||||
if (PassThru.IsPresent)
|
||||
{
|
||||
WriteObject(FolderId);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
ThrowTerminatingForException("RemoveInfisicalFolderCmdlet", "DeleteFolder", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using PSInfisicalAPI.Connections;
|
||||
using PSInfisicalAPI.Projects;
|
||||
|
||||
namespace PSInfisicalAPI.Cmdlets
|
||||
{
|
||||
[Cmdlet(VerbsCommon.Remove, "InfisicalProject", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)]
|
||||
public sealed class RemoveInfisicalProjectCmdlet : InfisicalCmdletBase
|
||||
{
|
||||
[Parameter(ValueFromPipelineByPropertyName = true, Position = 0)]
|
||||
[Alias("Id")]
|
||||
public string ProjectId { get; set; }
|
||||
|
||||
[Parameter] public SwitchParameter PassThru { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
try
|
||||
{
|
||||
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
|
||||
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
|
||||
|
||||
if (!ShouldProcess(resolvedProjectId, "Remove Infisical project"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
InfisicalProjectClient client = new InfisicalProjectClient(HttpClient, Logger);
|
||||
client.Delete(connection, resolvedProjectId);
|
||||
|
||||
if (PassThru.IsPresent)
|
||||
{
|
||||
WriteObject(resolvedProjectId);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
ThrowTerminatingForException("RemoveInfisicalProjectCmdlet", "DeleteProject", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using PSInfisicalAPI.Connections;
|
||||
using PSInfisicalAPI.Models;
|
||||
using PSInfisicalAPI.Secrets;
|
||||
|
||||
namespace PSInfisicalAPI.Cmdlets
|
||||
{
|
||||
[Cmdlet(VerbsCommon.Remove, "InfisicalSecret", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High, DefaultParameterSetName = "Single")]
|
||||
public sealed class RemoveInfisicalSecretCmdlet : InfisicalCmdletBase
|
||||
{
|
||||
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0, ParameterSetName = "Single")]
|
||||
public string SecretName { get; set; }
|
||||
|
||||
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "Bulk", ValueFromPipeline = true)]
|
||||
[Alias("Names", "SecretKeys")]
|
||||
public string[] SecretNames { get; set; }
|
||||
|
||||
[Parameter] public string ProjectId { get; set; }
|
||||
[Parameter] public string Environment { get; set; }
|
||||
[Parameter] public string SecretPath { get; set; }
|
||||
[Parameter] public string ApiVersion { get; set; }
|
||||
[Parameter] public InfisicalSecretType Type { get; set; } = InfisicalSecretType.Shared;
|
||||
[Parameter] public SwitchParameter PassThru { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
try
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
if (!ShouldProcess(SecretName, "Remove Infisical secret")) { return; }
|
||||
|
||||
InfisicalDeleteSecretRequest request = new InfisicalDeleteSecretRequest
|
||||
{
|
||||
SecretName = SecretName,
|
||||
ProjectId = resolvedProjectId,
|
||||
Environment = resolvedEnvironment,
|
||||
SecretPath = resolvedSecretPath,
|
||||
Type = Type.ToString(),
|
||||
ApiVersion = resolvedApiVersion
|
||||
};
|
||||
|
||||
client.Delete(connection, request);
|
||||
|
||||
if (PassThru.IsPresent)
|
||||
{
|
||||
WriteObject(SecretName);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
ThrowTerminatingForException("RemoveInfisicalSecretCmdlet", "DeleteSecret", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using PSInfisicalAPI.Connections;
|
||||
using PSInfisicalAPI.Tags;
|
||||
|
||||
namespace PSInfisicalAPI.Cmdlets
|
||||
{
|
||||
[Cmdlet(VerbsCommon.Remove, "InfisicalTag", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)]
|
||||
public sealed class RemoveInfisicalTagCmdlet : InfisicalCmdletBase
|
||||
{
|
||||
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0)]
|
||||
[Alias("Id")]
|
||||
public string TagId { get; set; }
|
||||
|
||||
[Parameter] public string ProjectId { get; set; }
|
||||
[Parameter] public SwitchParameter PassThru { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!ShouldProcess(TagId, "Remove Infisical tag"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
|
||||
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
|
||||
InfisicalTagClient client = new InfisicalTagClient(HttpClient, Logger);
|
||||
client.Delete(connection, resolvedProjectId, TagId);
|
||||
|
||||
if (PassThru.IsPresent)
|
||||
{
|
||||
WriteObject(TagId);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
ThrowTerminatingForException("RemoveInfisicalTagCmdlet", "DeleteTag", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using PSInfisicalAPI.Connections;
|
||||
using PSInfisicalAPI.Environments;
|
||||
using PSInfisicalAPI.Models;
|
||||
|
||||
namespace PSInfisicalAPI.Cmdlets
|
||||
{
|
||||
[Cmdlet(VerbsData.Update, "InfisicalEnvironment", SupportsShouldProcess = true)]
|
||||
[OutputType(typeof(InfisicalEnvironment))]
|
||||
public sealed class UpdateInfisicalEnvironmentCmdlet : InfisicalCmdletBase
|
||||
{
|
||||
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0)]
|
||||
[Alias("Id")]
|
||||
public string EnvironmentId { get; set; }
|
||||
|
||||
[Parameter] public string ProjectId { get; set; }
|
||||
[Parameter] public string Name { get; set; }
|
||||
[Parameter] public string Slug { get; set; }
|
||||
[Parameter] public int? Position { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!ShouldProcess(EnvironmentId, "Update Infisical environment"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
|
||||
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
|
||||
InfisicalEnvironmentClient client = new InfisicalEnvironmentClient(HttpClient, Logger);
|
||||
InfisicalEnvironment env = client.Update(connection, resolvedProjectId, EnvironmentId, Name, Slug, Position);
|
||||
if (env != null)
|
||||
{
|
||||
WriteObject(env);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
ThrowTerminatingForException("UpdateInfisicalEnvironmentCmdlet", "UpdateEnvironment", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using PSInfisicalAPI.Connections;
|
||||
using PSInfisicalAPI.Folders;
|
||||
using PSInfisicalAPI.Models;
|
||||
|
||||
namespace PSInfisicalAPI.Cmdlets
|
||||
{
|
||||
[Cmdlet(VerbsData.Update, "InfisicalFolder", SupportsShouldProcess = true)]
|
||||
[OutputType(typeof(InfisicalFolder))]
|
||||
public sealed class UpdateInfisicalFolderCmdlet : InfisicalCmdletBase
|
||||
{
|
||||
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0)]
|
||||
[Alias("Id")]
|
||||
public string FolderId { get; set; }
|
||||
|
||||
[Parameter(Mandatory = true, Position = 1)] public string Name { get; set; }
|
||||
[Parameter] public string ProjectId { get; set; }
|
||||
[Parameter] public string Environment { get; set; }
|
||||
[Parameter] public string Path { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!ShouldProcess(FolderId, "Update Infisical folder"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
|
||||
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
|
||||
string resolvedEnvironment = ResolveEnvironment(connection, Environment);
|
||||
string resolvedPath = ResolveSecretPath(connection, Path);
|
||||
InfisicalFolderClient client = new InfisicalFolderClient(HttpClient, Logger);
|
||||
InfisicalFolder folder = client.Update(connection, resolvedProjectId, resolvedEnvironment, FolderId, Name, resolvedPath);
|
||||
if (folder != null)
|
||||
{
|
||||
WriteObject(folder);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
ThrowTerminatingForException("UpdateInfisicalFolderCmdlet", "UpdateFolder", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using PSInfisicalAPI.Connections;
|
||||
using PSInfisicalAPI.Models;
|
||||
using PSInfisicalAPI.Projects;
|
||||
|
||||
namespace PSInfisicalAPI.Cmdlets
|
||||
{
|
||||
[Cmdlet(VerbsData.Update, "InfisicalProject", SupportsShouldProcess = true)]
|
||||
[OutputType(typeof(InfisicalProject))]
|
||||
public sealed class UpdateInfisicalProjectCmdlet : InfisicalCmdletBase
|
||||
{
|
||||
[Parameter(ValueFromPipelineByPropertyName = true, Position = 0)]
|
||||
[Alias("Id")]
|
||||
public string ProjectId { get; set; }
|
||||
|
||||
[Parameter] public string Name { get; set; }
|
||||
[Parameter] public string Description { get; set; }
|
||||
[Parameter] public bool? AutoCapitalization { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
try
|
||||
{
|
||||
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
|
||||
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
|
||||
|
||||
if (!ShouldProcess(resolvedProjectId, "Update Infisical project"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
InfisicalProjectClient client = new InfisicalProjectClient(HttpClient, Logger);
|
||||
InfisicalProject project = client.Update(connection, resolvedProjectId, Name, Description, AutoCapitalization);
|
||||
if (project != null)
|
||||
{
|
||||
WriteObject(project);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
ThrowTerminatingForException("UpdateInfisicalProjectCmdlet", "UpdateProject", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Management.Automation;
|
||||
using System.Security;
|
||||
using PSInfisicalAPI.Connections;
|
||||
using PSInfisicalAPI.Models;
|
||||
using PSInfisicalAPI.Secrets;
|
||||
using PSInfisicalAPI.Security;
|
||||
|
||||
namespace PSInfisicalAPI.Cmdlets
|
||||
{
|
||||
[Cmdlet(VerbsData.Update, "InfisicalSecret", SupportsShouldProcess = true, DefaultParameterSetName = "PlainText")]
|
||||
[OutputType(typeof(InfisicalSecret))]
|
||||
public sealed class UpdateInfisicalSecretCmdlet : InfisicalCmdletBase
|
||||
{
|
||||
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0, ParameterSetName = "PlainText")]
|
||||
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0, ParameterSetName = "SecureString")]
|
||||
public string SecretName { get; set; }
|
||||
|
||||
[Parameter(ParameterSetName = "PlainText")] public string SecretValue { get; set; }
|
||||
[Parameter(ParameterSetName = "SecureString")] public SecureString SecureSecretValue { get; set; }
|
||||
|
||||
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "Bulk", ValueFromPipeline = true)]
|
||||
[BulkSecretsTransformation]
|
||||
public IDictionary<string, string>[] Secrets { get; set; }
|
||||
|
||||
[Parameter] public string NewSecretName { get; set; }
|
||||
[Parameter] public string SecretComment { get; set; }
|
||||
[Parameter] public string ProjectId { get; set; }
|
||||
[Parameter] public string Environment { get; set; }
|
||||
[Parameter] public string SecretPath { get; set; }
|
||||
[Parameter] public string ApiVersion { get; set; }
|
||||
[Parameter] public InfisicalSecretType Type { get; set; } = InfisicalSecretType.Shared;
|
||||
[Parameter] public SwitchParameter SkipMultilineEncoding { get; set; }
|
||||
[Parameter] public string[] TagIds { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
try
|
||||
{
|
||||
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
|
||||
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
|
||||
string resolvedEnvironment = ResolveEnvironment(connection, Environment);
|
||||
string resolvedSecretPath = ResolveSecretPath(connection, SecretPath);
|
||||
string resolvedApiVersion = ResolveApiVersion(connection, ApiVersion);
|
||||
|
||||
if (string.Equals(ParameterSetName, "Bulk", StringComparison.Ordinal))
|
||||
{
|
||||
if (Secrets == null || Secrets.Length == 0) { return; }
|
||||
string target = string.Concat(Secrets.Length, " secret(s)");
|
||||
if (!ShouldProcess(target, "Bulk-update Infisical secrets")) { return; }
|
||||
|
||||
InfisicalBulkUpdateSecretsRequest bulk = new InfisicalBulkUpdateSecretsRequest
|
||||
{
|
||||
ProjectId = resolvedProjectId,
|
||||
Environment = resolvedEnvironment,
|
||||
SecretPath = resolvedSecretPath,
|
||||
ApiVersion = resolvedApiVersion,
|
||||
Secrets = InfisicalBulkSecretConverter.ToUpdateItems(Secrets)
|
||||
};
|
||||
|
||||
InfisicalSecretsClient bulkClient = new InfisicalSecretsClient(HttpClient, Logger);
|
||||
InfisicalSecret[] updated = bulkClient.UpdateBatch(connection, bulk);
|
||||
if (updated != null)
|
||||
{
|
||||
foreach (InfisicalSecret secret in updated) { WriteObject(secret); }
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ShouldProcess(SecretName, "Update Infisical secret")) { return; }
|
||||
|
||||
string plainValue = SecureSecretValue != null
|
||||
? SecureStringUtility.UsePlainText(SecureSecretValue, p => p)
|
||||
: SecretValue;
|
||||
|
||||
InfisicalUpdateSecretRequest request = new InfisicalUpdateSecretRequest
|
||||
{
|
||||
SecretName = SecretName,
|
||||
NewSecretName = NewSecretName,
|
||||
SecretValue = plainValue,
|
||||
SecretComment = SecretComment,
|
||||
ProjectId = resolvedProjectId,
|
||||
Environment = resolvedEnvironment,
|
||||
SecretPath = resolvedSecretPath,
|
||||
Type = Type.ToString(),
|
||||
ApiVersion = resolvedApiVersion,
|
||||
SkipMultilineEncoding = SkipMultilineEncoding.IsPresent ? (bool?)true : null,
|
||||
TagIds = TagIds
|
||||
};
|
||||
|
||||
InfisicalSecretsClient client = new InfisicalSecretsClient(HttpClient, Logger);
|
||||
InfisicalSecret single = client.Update(connection, request);
|
||||
if (single != null)
|
||||
{
|
||||
WriteObject(single);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
ThrowTerminatingForException("UpdateInfisicalSecretCmdlet", "UpdateSecret", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using PSInfisicalAPI.Connections;
|
||||
using PSInfisicalAPI.Models;
|
||||
using PSInfisicalAPI.Tags;
|
||||
|
||||
namespace PSInfisicalAPI.Cmdlets
|
||||
{
|
||||
[Cmdlet(VerbsData.Update, "InfisicalTag", SupportsShouldProcess = true)]
|
||||
[OutputType(typeof(InfisicalTag))]
|
||||
public sealed class UpdateInfisicalTagCmdlet : InfisicalCmdletBase
|
||||
{
|
||||
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0)]
|
||||
[Alias("Id")]
|
||||
public string TagId { get; set; }
|
||||
|
||||
[Parameter] public string Slug { get; set; }
|
||||
[Parameter] public string Name { get; set; }
|
||||
[Parameter] public string Color { get; set; }
|
||||
[Parameter] public string ProjectId { get; set; }
|
||||
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!ShouldProcess(TagId, "Update Infisical tag"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
InfisicalConnection connection = InfisicalSessionManager.RequireCurrent();
|
||||
string resolvedProjectId = ResolveProjectId(connection, ProjectId);
|
||||
InfisicalTagClient client = new InfisicalTagClient(HttpClient, Logger);
|
||||
InfisicalTag tag = client.Update(connection, resolvedProjectId, TagId, Slug, Name, Color);
|
||||
if (tag != null)
|
||||
{
|
||||
WriteObject(tag);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
ThrowTerminatingForException("UpdateInfisicalTagCmdlet", "UpdateTag", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,45 @@ namespace PSInfisicalAPI.Endpoints
|
||||
public static class InfisicalEndpointNames
|
||||
{
|
||||
public const string UniversalAuthLogin = "UniversalAuthLogin";
|
||||
public const string TokenAuthLogin = "TokenAuthLogin";
|
||||
public const string JwtAuthLogin = "JwtAuthLogin";
|
||||
public const string OidcAuthLogin = "OidcAuthLogin";
|
||||
public const string LdapAuthLogin = "LdapAuthLogin";
|
||||
public const string AzureAuthLogin = "AzureAuthLogin";
|
||||
public const string GcpIamAuthLogin = "GcpIamAuthLogin";
|
||||
|
||||
public const string ListSecrets = "ListSecrets";
|
||||
public const string RetrieveSecret = "RetrieveSecret";
|
||||
public const string CreateSecret = "CreateSecret";
|
||||
public const string UpdateSecret = "UpdateSecret";
|
||||
public const string DeleteSecret = "DeleteSecret";
|
||||
public const string BulkCreateSecret = "BulkCreateSecret";
|
||||
public const string BulkUpdateSecret = "BulkUpdateSecret";
|
||||
public const string BulkDeleteSecret = "BulkDeleteSecret";
|
||||
public const string DuplicateSecret = "DuplicateSecret";
|
||||
|
||||
public const string ListProjects = "ListProjects";
|
||||
public const string RetrieveProject = "RetrieveProject";
|
||||
public const string CreateProject = "CreateProject";
|
||||
public const string UpdateProject = "UpdateProject";
|
||||
public const string DeleteProject = "DeleteProject";
|
||||
|
||||
public const string ListEnvironments = "ListEnvironments";
|
||||
public const string RetrieveEnvironment = "RetrieveEnvironment";
|
||||
public const string CreateEnvironment = "CreateEnvironment";
|
||||
public const string UpdateEnvironment = "UpdateEnvironment";
|
||||
public const string DeleteEnvironment = "DeleteEnvironment";
|
||||
|
||||
public const string ListFolders = "ListFolders";
|
||||
public const string RetrieveFolder = "RetrieveFolder";
|
||||
public const string CreateFolder = "CreateFolder";
|
||||
public const string UpdateFolder = "UpdateFolder";
|
||||
public const string DeleteFolder = "DeleteFolder";
|
||||
|
||||
public const string ListTags = "ListTags";
|
||||
public const string RetrieveTag = "RetrieveTag";
|
||||
public const string CreateTag = "CreateTag";
|
||||
public const string UpdateTag = "UpdateTag";
|
||||
public const string DeleteTag = "DeleteTag";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,83 +5,495 @@ namespace PSInfisicalAPI.Endpoints
|
||||
{
|
||||
public static class InfisicalEndpointRegistry
|
||||
{
|
||||
private static readonly Dictionary<string, List<InfisicalEndpointDefinition>> Candidates =
|
||||
new Dictionary<string, List<InfisicalEndpointDefinition>>
|
||||
private static readonly Dictionary<string, List<InfisicalEndpointDefinition>> Candidates;
|
||||
|
||||
static InfisicalEndpointRegistry()
|
||||
{
|
||||
Candidates = new Dictionary<string, List<InfisicalEndpointDefinition>>();
|
||||
RegisterAuthentication(Candidates);
|
||||
RegisterSecrets(Candidates);
|
||||
RegisterProjects(Candidates);
|
||||
RegisterEnvironments(Candidates);
|
||||
RegisterFolders(Candidates);
|
||||
RegisterTags(Candidates);
|
||||
}
|
||||
|
||||
private static void Add(Dictionary<string, List<InfisicalEndpointDefinition>> map, InfisicalEndpointDefinition definition)
|
||||
{
|
||||
List<InfisicalEndpointDefinition> list;
|
||||
if (!map.TryGetValue(definition.Name, out list))
|
||||
{
|
||||
{
|
||||
InfisicalEndpointNames.UniversalAuthLogin,
|
||||
new List<InfisicalEndpointDefinition>
|
||||
{
|
||||
new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.UniversalAuthLogin,
|
||||
Resource = "Authentication",
|
||||
Version = "v1",
|
||||
Method = "POST",
|
||||
Template = "/api/v1/auth/universal-auth/login",
|
||||
RequiresAuthorization = false,
|
||||
ContainsSecretMaterialInRequest = true,
|
||||
ContainsSecretMaterialInResponse = true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
InfisicalEndpointNames.ListSecrets,
|
||||
new List<InfisicalEndpointDefinition>
|
||||
{
|
||||
new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.ListSecrets,
|
||||
Resource = "Secrets",
|
||||
Version = "v4",
|
||||
Method = "GET",
|
||||
Template = "/api/v4/secrets",
|
||||
RequiresAuthorization = true,
|
||||
ContainsSecretMaterialInRequest = false,
|
||||
ContainsSecretMaterialInResponse = true
|
||||
},
|
||||
new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.ListSecrets,
|
||||
Resource = "Secrets",
|
||||
Version = "v3",
|
||||
Method = "GET",
|
||||
Template = "/api/v3/secrets/raw",
|
||||
RequiresAuthorization = true,
|
||||
ContainsSecretMaterialInRequest = false,
|
||||
ContainsSecretMaterialInResponse = true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
InfisicalEndpointNames.RetrieveSecret,
|
||||
new List<InfisicalEndpointDefinition>
|
||||
{
|
||||
new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.RetrieveSecret,
|
||||
Resource = "Secrets",
|
||||
Version = "v4",
|
||||
Method = "GET",
|
||||
Template = "/api/v4/secrets/{secretName}",
|
||||
RequiresAuthorization = true,
|
||||
ContainsSecretMaterialInRequest = false,
|
||||
ContainsSecretMaterialInResponse = true
|
||||
},
|
||||
new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.RetrieveSecret,
|
||||
Resource = "Secrets",
|
||||
Version = "v3",
|
||||
Method = "GET",
|
||||
Template = "/api/v3/secrets/raw/{secretName}",
|
||||
RequiresAuthorization = true,
|
||||
ContainsSecretMaterialInRequest = false,
|
||||
ContainsSecretMaterialInResponse = true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
list = new List<InfisicalEndpointDefinition>();
|
||||
map[definition.Name] = list;
|
||||
}
|
||||
|
||||
list.Add(definition);
|
||||
}
|
||||
|
||||
private static void RegisterAuthentication(Dictionary<string, List<InfisicalEndpointDefinition>> map)
|
||||
{
|
||||
Add(map, new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.UniversalAuthLogin,
|
||||
Resource = "Authentication",
|
||||
Version = "v1",
|
||||
Method = "POST",
|
||||
Template = "/api/v1/auth/universal-auth/login",
|
||||
RequiresAuthorization = false,
|
||||
ContainsSecretMaterialInRequest = true,
|
||||
ContainsSecretMaterialInResponse = true
|
||||
});
|
||||
|
||||
Add(map, new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.TokenAuthLogin,
|
||||
Resource = "Authentication",
|
||||
Version = "v1",
|
||||
Method = "POST",
|
||||
Template = "/api/v1/auth/token-auth/login",
|
||||
RequiresAuthorization = false,
|
||||
ContainsSecretMaterialInRequest = true,
|
||||
ContainsSecretMaterialInResponse = true
|
||||
});
|
||||
|
||||
Add(map, new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.JwtAuthLogin,
|
||||
Resource = "Authentication",
|
||||
Version = "v1",
|
||||
Method = "POST",
|
||||
Template = "/api/v1/auth/jwt-auth/login",
|
||||
RequiresAuthorization = false,
|
||||
ContainsSecretMaterialInRequest = true,
|
||||
ContainsSecretMaterialInResponse = true
|
||||
});
|
||||
|
||||
Add(map, new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.OidcAuthLogin,
|
||||
Resource = "Authentication",
|
||||
Version = "v1",
|
||||
Method = "POST",
|
||||
Template = "/api/v1/auth/oidc-auth/login",
|
||||
RequiresAuthorization = false,
|
||||
ContainsSecretMaterialInRequest = true,
|
||||
ContainsSecretMaterialInResponse = true
|
||||
});
|
||||
|
||||
Add(map, new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.LdapAuthLogin,
|
||||
Resource = "Authentication",
|
||||
Version = "v1",
|
||||
Method = "POST",
|
||||
Template = "/api/v1/auth/ldap-auth/login",
|
||||
RequiresAuthorization = false,
|
||||
ContainsSecretMaterialInRequest = true,
|
||||
ContainsSecretMaterialInResponse = true
|
||||
});
|
||||
|
||||
Add(map, new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.AzureAuthLogin,
|
||||
Resource = "Authentication",
|
||||
Version = "v1",
|
||||
Method = "POST",
|
||||
Template = "/api/v1/auth/azure-auth/login",
|
||||
RequiresAuthorization = false,
|
||||
ContainsSecretMaterialInRequest = true,
|
||||
ContainsSecretMaterialInResponse = true
|
||||
});
|
||||
|
||||
Add(map, new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.GcpIamAuthLogin,
|
||||
Resource = "Authentication",
|
||||
Version = "v1",
|
||||
Method = "POST",
|
||||
Template = "/api/v1/auth/gcp-auth/login",
|
||||
RequiresAuthorization = false,
|
||||
ContainsSecretMaterialInRequest = true,
|
||||
ContainsSecretMaterialInResponse = true
|
||||
});
|
||||
}
|
||||
|
||||
private static void RegisterSecrets(Dictionary<string, List<InfisicalEndpointDefinition>> map)
|
||||
{
|
||||
Add(map, new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.ListSecrets,
|
||||
Resource = "Secrets",
|
||||
Version = "v4",
|
||||
Method = "GET",
|
||||
Template = "/api/v4/secrets",
|
||||
RequiresAuthorization = true,
|
||||
ContainsSecretMaterialInResponse = true
|
||||
});
|
||||
|
||||
Add(map, new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.ListSecrets,
|
||||
Resource = "Secrets",
|
||||
Version = "v3",
|
||||
Method = "GET",
|
||||
Template = "/api/v3/secrets/raw",
|
||||
RequiresAuthorization = true,
|
||||
ContainsSecretMaterialInResponse = true
|
||||
});
|
||||
|
||||
Add(map, new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.RetrieveSecret,
|
||||
Resource = "Secrets",
|
||||
Version = "v4",
|
||||
Method = "GET",
|
||||
Template = "/api/v4/secrets/{secretName}",
|
||||
RequiresAuthorization = true,
|
||||
ContainsSecretMaterialInResponse = true
|
||||
});
|
||||
|
||||
Add(map, new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.RetrieveSecret,
|
||||
Resource = "Secrets",
|
||||
Version = "v3",
|
||||
Method = "GET",
|
||||
Template = "/api/v3/secrets/raw/{secretName}",
|
||||
RequiresAuthorization = true,
|
||||
ContainsSecretMaterialInResponse = true
|
||||
});
|
||||
|
||||
Add(map, new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.CreateSecret,
|
||||
Resource = "Secrets",
|
||||
Version = "v3",
|
||||
Method = "POST",
|
||||
Template = "/api/v3/secrets/raw/{secretName}",
|
||||
RequiresAuthorization = true,
|
||||
ContainsSecretMaterialInRequest = true,
|
||||
ContainsSecretMaterialInResponse = true
|
||||
});
|
||||
|
||||
Add(map, new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.UpdateSecret,
|
||||
Resource = "Secrets",
|
||||
Version = "v3",
|
||||
Method = "PATCH",
|
||||
Template = "/api/v3/secrets/raw/{secretName}",
|
||||
RequiresAuthorization = true,
|
||||
ContainsSecretMaterialInRequest = true,
|
||||
ContainsSecretMaterialInResponse = true
|
||||
});
|
||||
|
||||
Add(map, new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.DeleteSecret,
|
||||
Resource = "Secrets",
|
||||
Version = "v3",
|
||||
Method = "DELETE",
|
||||
Template = "/api/v3/secrets/raw/{secretName}",
|
||||
RequiresAuthorization = true,
|
||||
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,
|
||||
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 = "v4",
|
||||
Method = "PATCH",
|
||||
Template = "/api/v4/secrets/batch",
|
||||
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 = "v4",
|
||||
Method = "DELETE",
|
||||
Template = "/api/v4/secrets/batch",
|
||||
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)
|
||||
{
|
||||
Add(map, new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.ListProjects,
|
||||
Resource = "Projects",
|
||||
Version = "v1",
|
||||
Method = "GET",
|
||||
Template = "/api/v1/workspace",
|
||||
RequiresAuthorization = true
|
||||
});
|
||||
|
||||
Add(map, new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.RetrieveProject,
|
||||
Resource = "Projects",
|
||||
Version = "v1",
|
||||
Method = "GET",
|
||||
Template = "/api/v1/workspace/{projectId}",
|
||||
RequiresAuthorization = true
|
||||
});
|
||||
|
||||
Add(map, new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.CreateProject,
|
||||
Resource = "Projects",
|
||||
Version = "v2",
|
||||
Method = "POST",
|
||||
Template = "/api/v2/workspace",
|
||||
RequiresAuthorization = true
|
||||
});
|
||||
|
||||
Add(map, new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.UpdateProject,
|
||||
Resource = "Projects",
|
||||
Version = "v1",
|
||||
Method = "PATCH",
|
||||
Template = "/api/v1/workspace/{projectId}",
|
||||
RequiresAuthorization = true
|
||||
});
|
||||
|
||||
Add(map, new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.DeleteProject,
|
||||
Resource = "Projects",
|
||||
Version = "v1",
|
||||
Method = "DELETE",
|
||||
Template = "/api/v1/workspace/{projectId}",
|
||||
RequiresAuthorization = true
|
||||
});
|
||||
}
|
||||
|
||||
private static void RegisterEnvironments(Dictionary<string, List<InfisicalEndpointDefinition>> map)
|
||||
{
|
||||
Add(map, new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.ListEnvironments,
|
||||
Resource = "Environments",
|
||||
Version = "v1",
|
||||
Method = "GET",
|
||||
Template = "/api/v1/workspace/{projectId}",
|
||||
RequiresAuthorization = true
|
||||
});
|
||||
|
||||
Add(map, new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.RetrieveEnvironment,
|
||||
Resource = "Environments",
|
||||
Version = "v1",
|
||||
Method = "GET",
|
||||
Template = "/api/v1/workspace/{projectId}/environments/{environmentId}",
|
||||
RequiresAuthorization = true
|
||||
});
|
||||
|
||||
Add(map, new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.CreateEnvironment,
|
||||
Resource = "Environments",
|
||||
Version = "v1",
|
||||
Method = "POST",
|
||||
Template = "/api/v1/workspace/{projectId}/environments",
|
||||
RequiresAuthorization = true
|
||||
});
|
||||
|
||||
Add(map, new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.UpdateEnvironment,
|
||||
Resource = "Environments",
|
||||
Version = "v1",
|
||||
Method = "PATCH",
|
||||
Template = "/api/v1/workspace/{projectId}/environments/{environmentId}",
|
||||
RequiresAuthorization = true
|
||||
});
|
||||
|
||||
Add(map, new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.DeleteEnvironment,
|
||||
Resource = "Environments",
|
||||
Version = "v1",
|
||||
Method = "DELETE",
|
||||
Template = "/api/v1/workspace/{projectId}/environments/{environmentId}",
|
||||
RequiresAuthorization = true
|
||||
});
|
||||
}
|
||||
|
||||
private static void RegisterFolders(Dictionary<string, List<InfisicalEndpointDefinition>> map)
|
||||
{
|
||||
Add(map, new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.ListFolders,
|
||||
Resource = "Folders",
|
||||
Version = "v1",
|
||||
Method = "GET",
|
||||
Template = "/api/v1/folders",
|
||||
RequiresAuthorization = true
|
||||
});
|
||||
|
||||
Add(map, new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.RetrieveFolder,
|
||||
Resource = "Folders",
|
||||
Version = "v1",
|
||||
Method = "GET",
|
||||
Template = "/api/v1/folders/{folderId}",
|
||||
RequiresAuthorization = true
|
||||
});
|
||||
|
||||
Add(map, new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.CreateFolder,
|
||||
Resource = "Folders",
|
||||
Version = "v1",
|
||||
Method = "POST",
|
||||
Template = "/api/v1/folders",
|
||||
RequiresAuthorization = true
|
||||
});
|
||||
|
||||
Add(map, new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.UpdateFolder,
|
||||
Resource = "Folders",
|
||||
Version = "v1",
|
||||
Method = "PATCH",
|
||||
Template = "/api/v1/folders/{folderId}",
|
||||
RequiresAuthorization = true
|
||||
});
|
||||
|
||||
Add(map, new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.DeleteFolder,
|
||||
Resource = "Folders",
|
||||
Version = "v1",
|
||||
Method = "DELETE",
|
||||
Template = "/api/v1/folders/{folderId}",
|
||||
RequiresAuthorization = true
|
||||
});
|
||||
}
|
||||
|
||||
private static void RegisterTags(Dictionary<string, List<InfisicalEndpointDefinition>> map)
|
||||
{
|
||||
Add(map, new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.ListTags,
|
||||
Resource = "Tags",
|
||||
Version = "v1",
|
||||
Method = "GET",
|
||||
Template = "/api/v1/workspace/{projectId}/tags",
|
||||
RequiresAuthorization = true
|
||||
});
|
||||
|
||||
Add(map, new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.RetrieveTag,
|
||||
Resource = "Tags",
|
||||
Version = "v1",
|
||||
Method = "GET",
|
||||
Template = "/api/v1/workspace/{projectId}/tags/{tagId}",
|
||||
RequiresAuthorization = true
|
||||
});
|
||||
|
||||
Add(map, new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.CreateTag,
|
||||
Resource = "Tags",
|
||||
Version = "v1",
|
||||
Method = "POST",
|
||||
Template = "/api/v1/workspace/{projectId}/tags",
|
||||
RequiresAuthorization = true
|
||||
});
|
||||
|
||||
Add(map, new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.UpdateTag,
|
||||
Resource = "Tags",
|
||||
Version = "v1",
|
||||
Method = "PATCH",
|
||||
Template = "/api/v1/workspace/{projectId}/tags/{tagId}",
|
||||
RequiresAuthorization = true
|
||||
});
|
||||
|
||||
Add(map, new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.DeleteTag,
|
||||
Resource = "Tags",
|
||||
Version = "v1",
|
||||
Method = "DELETE",
|
||||
Template = "/api/v1/workspace/{projectId}/tags/{tagId}",
|
||||
RequiresAuthorization = true
|
||||
});
|
||||
}
|
||||
|
||||
public static InfisicalEndpointDefinition Get(string name)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using PSInfisicalAPI.Connections;
|
||||
using PSInfisicalAPI.Endpoints;
|
||||
using PSInfisicalAPI.Errors;
|
||||
using PSInfisicalAPI.Http;
|
||||
using PSInfisicalAPI.Logging;
|
||||
using PSInfisicalAPI.Models;
|
||||
using PSInfisicalAPI.Serialization;
|
||||
|
||||
namespace PSInfisicalAPI.Environments
|
||||
{
|
||||
public sealed class InfisicalEnvironmentClient
|
||||
{
|
||||
private const string Component = "EnvironmentClient";
|
||||
|
||||
private readonly IInfisicalLogger _logger;
|
||||
private readonly JsonInfisicalSerializer _serializer;
|
||||
private readonly InfisicalApiInvoker _invoker;
|
||||
|
||||
public InfisicalEnvironmentClient(IInfisicalHttpClient httpClient, IInfisicalLogger logger)
|
||||
{
|
||||
if (httpClient == null) { throw new ArgumentNullException(nameof(httpClient)); }
|
||||
_logger = logger ?? NullInfisicalLogger.Instance;
|
||||
_serializer = new JsonInfisicalSerializer();
|
||||
_invoker = new InfisicalApiInvoker(httpClient);
|
||||
}
|
||||
|
||||
public InfisicalEnvironment[] List(InfisicalConnection connection, string projectId)
|
||||
{
|
||||
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
|
||||
string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId);
|
||||
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
|
||||
|
||||
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "projectId", resolvedProjectId } };
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Information(Component, "Attempting to list Infisical environments. Please Wait...");
|
||||
InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.ListEnvironments, "ListEnvironments", pathParameters, null, null);
|
||||
InfisicalEnvironmentWorkspaceWrapperDto dto = _serializer.Deserialize<InfisicalEnvironmentWorkspaceWrapperDto>(response.Body);
|
||||
response.Clear();
|
||||
|
||||
InfisicalEnvironmentWorkspaceDto workspace = dto != null ? (dto.Workspace ?? dto.Project) : null;
|
||||
List<InfisicalEnvironmentResponseDto> envs = workspace != null ? workspace.Environments : null;
|
||||
InfisicalEnvironment[] mapped = InfisicalEnvironmentMapper.MapMany(envs, resolvedProjectId);
|
||||
_logger.Information(Component, "Infisical environment list retrieval was successful.");
|
||||
return mapped;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_logger.Error(Component, "Infisical environment list retrieval failed.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public InfisicalEnvironment Retrieve(InfisicalConnection connection, string projectId, string environmentSlugOrId)
|
||||
{
|
||||
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
|
||||
string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId);
|
||||
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
|
||||
if (string.IsNullOrEmpty(environmentSlugOrId)) { throw new InfisicalConfigurationException("Environment is required."); }
|
||||
|
||||
InfisicalEnvironment[] all = List(connection, resolvedProjectId);
|
||||
foreach (InfisicalEnvironment env in all)
|
||||
{
|
||||
if (string.Equals(env.Id, environmentSlugOrId, StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(env.Slug, environmentSlugOrId, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return env;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public InfisicalEnvironment Create(InfisicalConnection connection, string projectId, string name, string slug, int? position)
|
||||
{
|
||||
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
|
||||
string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId);
|
||||
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
|
||||
if (string.IsNullOrEmpty(name)) { throw new InfisicalConfigurationException("Name is required."); }
|
||||
if (string.IsNullOrEmpty(slug)) { throw new InfisicalConfigurationException("Slug is required."); }
|
||||
|
||||
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "projectId", resolvedProjectId } };
|
||||
InfisicalEnvironmentCreateRequestDto request = new InfisicalEnvironmentCreateRequestDto { Name = name, Slug = slug, Position = position };
|
||||
string body = _serializer.Serialize(request);
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Information(Component, string.Concat("Attempting to create Infisical environment '", slug, "'. Please Wait..."));
|
||||
InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.CreateEnvironment, "CreateEnvironment", pathParameters, null, body);
|
||||
InfisicalEnvironmentSingleResponseDto dto = _serializer.Deserialize<InfisicalEnvironmentSingleResponseDto>(response.Body);
|
||||
response.Clear();
|
||||
|
||||
InfisicalEnvironment mapped = InfisicalEnvironmentMapper.Map(dto != null ? dto.Environment : null, resolvedProjectId);
|
||||
_logger.Information(Component, "Infisical environment creation was successful.");
|
||||
return mapped;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_logger.Error(Component, "Infisical environment creation failed.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public InfisicalEnvironment Update(InfisicalConnection connection, string projectId, string environmentId, string name, string slug, int? position)
|
||||
{
|
||||
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
|
||||
string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId);
|
||||
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
|
||||
if (string.IsNullOrEmpty(environmentId)) { throw new InfisicalConfigurationException("EnvironmentId is required."); }
|
||||
|
||||
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "projectId", resolvedProjectId }, { "environmentId", environmentId } };
|
||||
InfisicalEnvironmentUpdateRequestDto request = new InfisicalEnvironmentUpdateRequestDto { Name = name, Slug = slug, Position = position };
|
||||
string body = _serializer.Serialize(request);
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Information(Component, string.Concat("Attempting to update Infisical environment '", environmentId, "'. Please Wait..."));
|
||||
InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.UpdateEnvironment, "UpdateEnvironment", pathParameters, null, body);
|
||||
InfisicalEnvironmentSingleResponseDto dto = _serializer.Deserialize<InfisicalEnvironmentSingleResponseDto>(response.Body);
|
||||
response.Clear();
|
||||
|
||||
InfisicalEnvironment mapped = InfisicalEnvironmentMapper.Map(dto != null ? dto.Environment : null, resolvedProjectId);
|
||||
_logger.Information(Component, "Infisical environment update was successful.");
|
||||
return mapped;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_logger.Error(Component, "Infisical environment update failed.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public void Delete(InfisicalConnection connection, string projectId, string environmentId)
|
||||
{
|
||||
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
|
||||
string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId);
|
||||
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
|
||||
if (string.IsNullOrEmpty(environmentId)) { throw new InfisicalConfigurationException("EnvironmentId is required."); }
|
||||
|
||||
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "projectId", resolvedProjectId }, { "environmentId", environmentId } };
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Information(Component, string.Concat("Attempting to delete Infisical environment '", environmentId, "'. Please Wait..."));
|
||||
InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.DeleteEnvironment, "DeleteEnvironment", pathParameters, null, null);
|
||||
response.Clear();
|
||||
_logger.Information(Component, "Infisical environment deletion was successful.");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_logger.Error(Component, "Infisical environment deletion failed.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static string FirstNonEmpty(params string[] values)
|
||||
{
|
||||
if (values == null) { return null; }
|
||||
foreach (string value in values) { if (!string.IsNullOrEmpty(value)) { return value; } }
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PSInfisicalAPI.Environments
|
||||
{
|
||||
internal sealed class InfisicalEnvironmentResponseDto
|
||||
{
|
||||
[JsonProperty("id")] public string Id { get; set; }
|
||||
[JsonProperty("_id")] public string InternalId { get; set; }
|
||||
[JsonProperty("name")] public string Name { get; set; }
|
||||
[JsonProperty("slug")] public string Slug { get; set; }
|
||||
[JsonProperty("position")] public int? Position { get; set; }
|
||||
[JsonProperty("projectId")] public string ProjectId { get; set; }
|
||||
[JsonProperty("workspaceId")] public string WorkspaceId { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class InfisicalEnvironmentSingleResponseDto
|
||||
{
|
||||
[JsonProperty("environment")] public InfisicalEnvironmentResponseDto Environment { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class InfisicalEnvironmentWorkspaceWrapperDto
|
||||
{
|
||||
[JsonProperty("workspace")] public InfisicalEnvironmentWorkspaceDto Workspace { get; set; }
|
||||
[JsonProperty("project")] public InfisicalEnvironmentWorkspaceDto Project { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class InfisicalEnvironmentWorkspaceDto
|
||||
{
|
||||
[JsonProperty("id")] public string Id { get; set; }
|
||||
[JsonProperty("environments")] public List<InfisicalEnvironmentResponseDto> Environments { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class InfisicalEnvironmentCreateRequestDto
|
||||
{
|
||||
[JsonProperty("name")] public string Name { get; set; }
|
||||
[JsonProperty("slug")] public string Slug { get; set; }
|
||||
[JsonProperty("position", NullValueHandling = NullValueHandling.Ignore)] public int? Position { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class InfisicalEnvironmentUpdateRequestDto
|
||||
{
|
||||
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] public string Name { get; set; }
|
||||
[JsonProperty("slug", NullValueHandling = NullValueHandling.Ignore)] public string Slug { get; set; }
|
||||
[JsonProperty("position", NullValueHandling = NullValueHandling.Ignore)] public int? Position { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using PSInfisicalAPI.Models;
|
||||
|
||||
namespace PSInfisicalAPI.Environments
|
||||
{
|
||||
internal static class InfisicalEnvironmentMapper
|
||||
{
|
||||
public static InfisicalEnvironment Map(InfisicalEnvironmentResponseDto dto, string fallbackProjectId)
|
||||
{
|
||||
if (dto == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string projectId = !string.IsNullOrEmpty(dto.ProjectId)
|
||||
? dto.ProjectId
|
||||
: (!string.IsNullOrEmpty(dto.WorkspaceId) ? dto.WorkspaceId : fallbackProjectId);
|
||||
|
||||
return new InfisicalEnvironment
|
||||
{
|
||||
Id = !string.IsNullOrEmpty(dto.Id) ? dto.Id : dto.InternalId,
|
||||
Name = dto.Name,
|
||||
Slug = dto.Slug,
|
||||
Position = dto.Position,
|
||||
ProjectId = projectId
|
||||
};
|
||||
}
|
||||
|
||||
public static InfisicalEnvironment[] MapMany(IEnumerable<InfisicalEnvironmentResponseDto> items, string fallbackProjectId)
|
||||
{
|
||||
if (items == null)
|
||||
{
|
||||
return Array.Empty<InfisicalEnvironment>();
|
||||
}
|
||||
|
||||
List<InfisicalEnvironment> results = new List<InfisicalEnvironment>();
|
||||
foreach (InfisicalEnvironmentResponseDto dto in items)
|
||||
{
|
||||
InfisicalEnvironment mapped = Map(dto, fallbackProjectId);
|
||||
if (mapped != null)
|
||||
{
|
||||
results.Add(mapped);
|
||||
}
|
||||
}
|
||||
|
||||
return results.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using PSInfisicalAPI.Connections;
|
||||
using PSInfisicalAPI.Endpoints;
|
||||
using PSInfisicalAPI.Errors;
|
||||
using PSInfisicalAPI.Http;
|
||||
using PSInfisicalAPI.Logging;
|
||||
using PSInfisicalAPI.Models;
|
||||
using PSInfisicalAPI.Serialization;
|
||||
|
||||
namespace PSInfisicalAPI.Folders
|
||||
{
|
||||
public sealed class InfisicalFolderClient
|
||||
{
|
||||
private const string Component = "FolderClient";
|
||||
|
||||
private readonly IInfisicalLogger _logger;
|
||||
private readonly JsonInfisicalSerializer _serializer;
|
||||
private readonly InfisicalApiInvoker _invoker;
|
||||
|
||||
public InfisicalFolderClient(IInfisicalHttpClient httpClient, IInfisicalLogger logger)
|
||||
{
|
||||
if (httpClient == null) { throw new ArgumentNullException(nameof(httpClient)); }
|
||||
_logger = logger ?? NullInfisicalLogger.Instance;
|
||||
_serializer = new JsonInfisicalSerializer();
|
||||
_invoker = new InfisicalApiInvoker(httpClient);
|
||||
}
|
||||
|
||||
public InfisicalFolder[] List(InfisicalConnection connection, string projectId, string environment, string path)
|
||||
{
|
||||
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
|
||||
string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId);
|
||||
string resolvedEnvironment = FirstNonEmpty(environment, connection.Environment);
|
||||
string resolvedPath = FirstNonEmpty(path, connection.DefaultSecretPath, "/");
|
||||
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
|
||||
if (string.IsNullOrEmpty(resolvedEnvironment)) { throw new InfisicalConfigurationException("Environment is required."); }
|
||||
|
||||
List<KeyValuePair<string, string>> queryParameters = new List<KeyValuePair<string, string>>
|
||||
{
|
||||
new KeyValuePair<string, string>("workspaceId", resolvedProjectId),
|
||||
new KeyValuePair<string, string>("environment", resolvedEnvironment),
|
||||
new KeyValuePair<string, string>("path", resolvedPath)
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Information(Component, "Attempting to list Infisical folders. Please Wait...");
|
||||
InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.ListFolders, "ListFolders", null, queryParameters, null);
|
||||
InfisicalFolderListResponseDto dto = _serializer.Deserialize<InfisicalFolderListResponseDto>(response.Body);
|
||||
response.Clear();
|
||||
|
||||
InfisicalFolder[] mapped = InfisicalFolderMapper.MapMany(dto != null ? dto.Folders : null, resolvedProjectId, resolvedEnvironment);
|
||||
_logger.Information(Component, "Infisical folder list retrieval was successful.");
|
||||
return mapped;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_logger.Error(Component, "Infisical folder list retrieval failed.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public InfisicalFolder Retrieve(InfisicalConnection connection, string projectId, string environment, string path, string folderNameOrId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(folderNameOrId)) { throw new InfisicalConfigurationException("Folder name or id is required."); }
|
||||
|
||||
InfisicalFolder[] all = List(connection, projectId, environment, path);
|
||||
foreach (InfisicalFolder folder in all)
|
||||
{
|
||||
if (string.Equals(folder.Id, folderNameOrId, StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(folder.Name, folderNameOrId, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return folder;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public InfisicalFolder Create(InfisicalConnection connection, string projectId, string environment, string name, string path)
|
||||
{
|
||||
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
|
||||
string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId);
|
||||
string resolvedEnvironment = FirstNonEmpty(environment, connection.Environment);
|
||||
string resolvedPath = FirstNonEmpty(path, connection.DefaultSecretPath, "/");
|
||||
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
|
||||
if (string.IsNullOrEmpty(resolvedEnvironment)) { throw new InfisicalConfigurationException("Environment is required."); }
|
||||
if (string.IsNullOrEmpty(name)) { throw new InfisicalConfigurationException("Name is required."); }
|
||||
|
||||
InfisicalFolderCreateRequestDto request = new InfisicalFolderCreateRequestDto
|
||||
{
|
||||
WorkspaceId = resolvedProjectId,
|
||||
Environment = resolvedEnvironment,
|
||||
Name = name,
|
||||
Path = resolvedPath
|
||||
};
|
||||
string body = _serializer.Serialize(request);
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Information(Component, string.Concat("Attempting to create Infisical folder '", name, "'. Please Wait..."));
|
||||
InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.CreateFolder, "CreateFolder", null, null, body);
|
||||
InfisicalFolderSingleResponseDto dto = _serializer.Deserialize<InfisicalFolderSingleResponseDto>(response.Body);
|
||||
response.Clear();
|
||||
|
||||
InfisicalFolder mapped = InfisicalFolderMapper.Map(dto != null ? dto.Folder : null, resolvedProjectId, resolvedEnvironment);
|
||||
_logger.Information(Component, "Infisical folder creation was successful.");
|
||||
return mapped;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_logger.Error(Component, "Infisical folder creation failed.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public InfisicalFolder Update(InfisicalConnection connection, string projectId, string environment, string folderId, string name, string path)
|
||||
{
|
||||
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
|
||||
string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId);
|
||||
string resolvedEnvironment = FirstNonEmpty(environment, connection.Environment);
|
||||
string resolvedPath = FirstNonEmpty(path, connection.DefaultSecretPath, "/");
|
||||
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
|
||||
if (string.IsNullOrEmpty(resolvedEnvironment)) { throw new InfisicalConfigurationException("Environment is required."); }
|
||||
if (string.IsNullOrEmpty(folderId)) { throw new InfisicalConfigurationException("FolderId is required."); }
|
||||
if (string.IsNullOrEmpty(name)) { throw new InfisicalConfigurationException("Name is required."); }
|
||||
|
||||
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "folderId", folderId } };
|
||||
InfisicalFolderUpdateRequestDto request = new InfisicalFolderUpdateRequestDto
|
||||
{
|
||||
WorkspaceId = resolvedProjectId,
|
||||
Environment = resolvedEnvironment,
|
||||
Name = name,
|
||||
Path = resolvedPath
|
||||
};
|
||||
string body = _serializer.Serialize(request);
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Information(Component, string.Concat("Attempting to update Infisical folder '", folderId, "'. Please Wait..."));
|
||||
InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.UpdateFolder, "UpdateFolder", pathParameters, null, body);
|
||||
InfisicalFolderSingleResponseDto dto = _serializer.Deserialize<InfisicalFolderSingleResponseDto>(response.Body);
|
||||
response.Clear();
|
||||
|
||||
InfisicalFolder mapped = InfisicalFolderMapper.Map(dto != null ? dto.Folder : null, resolvedProjectId, resolvedEnvironment);
|
||||
_logger.Information(Component, "Infisical folder update was successful.");
|
||||
return mapped;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_logger.Error(Component, "Infisical folder update failed.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public void Delete(InfisicalConnection connection, string projectId, string environment, string folderId, string path)
|
||||
{
|
||||
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
|
||||
string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId);
|
||||
string resolvedEnvironment = FirstNonEmpty(environment, connection.Environment);
|
||||
string resolvedPath = FirstNonEmpty(path, connection.DefaultSecretPath, "/");
|
||||
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
|
||||
if (string.IsNullOrEmpty(resolvedEnvironment)) { throw new InfisicalConfigurationException("Environment is required."); }
|
||||
if (string.IsNullOrEmpty(folderId)) { throw new InfisicalConfigurationException("FolderId is required."); }
|
||||
|
||||
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "folderId", folderId } };
|
||||
List<KeyValuePair<string, string>> queryParameters = new List<KeyValuePair<string, string>>
|
||||
{
|
||||
new KeyValuePair<string, string>("workspaceId", resolvedProjectId),
|
||||
new KeyValuePair<string, string>("environment", resolvedEnvironment),
|
||||
new KeyValuePair<string, string>("path", resolvedPath)
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Information(Component, string.Concat("Attempting to delete Infisical folder '", folderId, "'. Please Wait..."));
|
||||
InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.DeleteFolder, "DeleteFolder", pathParameters, queryParameters, null);
|
||||
response.Clear();
|
||||
_logger.Information(Component, "Infisical folder deletion was successful.");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_logger.Error(Component, "Infisical folder deletion failed.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static string FirstNonEmpty(params string[] values)
|
||||
{
|
||||
if (values == null) { return null; }
|
||||
foreach (string value in values) { if (!string.IsNullOrEmpty(value)) { return value; } }
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PSInfisicalAPI.Folders
|
||||
{
|
||||
internal sealed class InfisicalFolderResponseDto
|
||||
{
|
||||
[JsonProperty("id")] public string Id { get; set; }
|
||||
[JsonProperty("_id")] public string InternalId { get; set; }
|
||||
[JsonProperty("name")] public string Name { get; set; }
|
||||
[JsonProperty("path")] public string Path { get; set; }
|
||||
[JsonProperty("parentId")] public string ParentId { get; set; }
|
||||
[JsonProperty("envId")] public string EnvId { get; set; }
|
||||
[JsonProperty("environment")] public string Environment { get; set; }
|
||||
[JsonProperty("projectId")] public string ProjectId { get; set; }
|
||||
[JsonProperty("workspaceId")] public string WorkspaceId { get; set; }
|
||||
[JsonProperty("createdAt")] public string CreatedAt { get; set; }
|
||||
[JsonProperty("updatedAt")] public string UpdatedAt { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class InfisicalFolderListResponseDto
|
||||
{
|
||||
[JsonProperty("folders")] public List<InfisicalFolderResponseDto> Folders { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class InfisicalFolderSingleResponseDto
|
||||
{
|
||||
[JsonProperty("folder")] public InfisicalFolderResponseDto Folder { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class InfisicalFolderCreateRequestDto
|
||||
{
|
||||
[JsonProperty("workspaceId")] public string WorkspaceId { get; set; }
|
||||
[JsonProperty("environment")] public string Environment { get; set; }
|
||||
[JsonProperty("name")] public string Name { get; set; }
|
||||
[JsonProperty("path", NullValueHandling = NullValueHandling.Ignore)] public string Path { get; set; }
|
||||
[JsonProperty("directory", NullValueHandling = NullValueHandling.Ignore)] public string Directory { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class InfisicalFolderUpdateRequestDto
|
||||
{
|
||||
[JsonProperty("workspaceId")] public string WorkspaceId { get; set; }
|
||||
[JsonProperty("environment")] public string Environment { get; set; }
|
||||
[JsonProperty("name")] public string Name { get; set; }
|
||||
[JsonProperty("path", NullValueHandling = NullValueHandling.Ignore)] public string Path { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using PSInfisicalAPI.Models;
|
||||
|
||||
namespace PSInfisicalAPI.Folders
|
||||
{
|
||||
internal static class InfisicalFolderMapper
|
||||
{
|
||||
public static InfisicalFolder Map(InfisicalFolderResponseDto dto, string fallbackProjectId, string fallbackEnvironment)
|
||||
{
|
||||
if (dto == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string projectId = !string.IsNullOrEmpty(dto.ProjectId)
|
||||
? dto.ProjectId
|
||||
: (!string.IsNullOrEmpty(dto.WorkspaceId) ? dto.WorkspaceId : fallbackProjectId);
|
||||
|
||||
string environment = !string.IsNullOrEmpty(dto.Environment) ? dto.Environment : fallbackEnvironment;
|
||||
|
||||
return new InfisicalFolder
|
||||
{
|
||||
Id = !string.IsNullOrEmpty(dto.Id) ? dto.Id : dto.InternalId,
|
||||
Name = dto.Name,
|
||||
Path = dto.Path,
|
||||
ParentId = dto.ParentId,
|
||||
Environment = environment,
|
||||
ProjectId = projectId,
|
||||
CreatedAtUtc = ParseTimestamp(dto.CreatedAt),
|
||||
UpdatedAtUtc = ParseTimestamp(dto.UpdatedAt)
|
||||
};
|
||||
}
|
||||
|
||||
public static InfisicalFolder[] MapMany(IEnumerable<InfisicalFolderResponseDto> items, string fallbackProjectId, string fallbackEnvironment)
|
||||
{
|
||||
if (items == null)
|
||||
{
|
||||
return Array.Empty<InfisicalFolder>();
|
||||
}
|
||||
|
||||
List<InfisicalFolder> results = new List<InfisicalFolder>();
|
||||
foreach (InfisicalFolderResponseDto dto in items)
|
||||
{
|
||||
InfisicalFolder mapped = Map(dto, fallbackProjectId, fallbackEnvironment);
|
||||
if (mapped != null)
|
||||
{
|
||||
results.Add(mapped);
|
||||
}
|
||||
}
|
||||
|
||||
return results.ToArray();
|
||||
}
|
||||
|
||||
private static DateTimeOffset? ParseTimestamp(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
DateTimeOffset parsed;
|
||||
if (DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out parsed))
|
||||
{
|
||||
return parsed;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using PSInfisicalAPI.Connections;
|
||||
using PSInfisicalAPI.Endpoints;
|
||||
using PSInfisicalAPI.Errors;
|
||||
using PSInfisicalAPI.Security;
|
||||
|
||||
namespace PSInfisicalAPI.Http
|
||||
{
|
||||
internal sealed class InfisicalApiInvoker
|
||||
{
|
||||
private readonly IInfisicalHttpClient _httpClient;
|
||||
|
||||
public InfisicalApiInvoker(IInfisicalHttpClient httpClient)
|
||||
{
|
||||
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
|
||||
}
|
||||
|
||||
public InfisicalHttpResponse Invoke(
|
||||
InfisicalConnection connection,
|
||||
string endpointName,
|
||||
string operationName,
|
||||
IDictionary<string, string> pathParameters,
|
||||
IEnumerable<KeyValuePair<string, string>> queryParameters,
|
||||
string body)
|
||||
{
|
||||
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
|
||||
if (string.IsNullOrEmpty(endpointName)) { throw new ArgumentNullException(nameof(endpointName)); }
|
||||
|
||||
InfisicalEndpointDefinition definition = InfisicalEndpointRegistry.Get(endpointName);
|
||||
Uri uri = InfisicalUriBuilder.Build(connection.BaseUri, definition, pathParameters, queryParameters);
|
||||
|
||||
InfisicalHttpResponse response = ExecuteAuthorized(connection, definition, operationName, uri, body);
|
||||
|
||||
if (response.StatusCode >= 200 && response.StatusCode < 300)
|
||||
{
|
||||
return response;
|
||||
}
|
||||
|
||||
InfisicalApiException exception = BuildApiException(response, definition);
|
||||
response.Clear();
|
||||
throw exception;
|
||||
}
|
||||
|
||||
private InfisicalHttpResponse ExecuteAuthorized(
|
||||
InfisicalConnection connection,
|
||||
InfisicalEndpointDefinition definition,
|
||||
string operationName,
|
||||
Uri uri,
|
||||
string body)
|
||||
{
|
||||
Dictionary<string, string> headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
headers["Accept"] = "application/json";
|
||||
|
||||
if (!string.IsNullOrEmpty(body))
|
||||
{
|
||||
headers["Content-Type"] = "application/json";
|
||||
}
|
||||
|
||||
if (definition.RequiresAuthorization)
|
||||
{
|
||||
if (connection.AccessToken == null)
|
||||
{
|
||||
throw new InfisicalAuthenticationException("Connection does not contain an access token.");
|
||||
}
|
||||
|
||||
SecureStringUtility.UsePlainText(connection.AccessToken, plainToken =>
|
||||
{
|
||||
headers["Authorization"] = string.Concat("Bearer ", plainToken ?? string.Empty);
|
||||
});
|
||||
}
|
||||
|
||||
InfisicalHttpRequest request = new InfisicalHttpRequest
|
||||
{
|
||||
OperationName = operationName,
|
||||
EndpointName = definition.Name,
|
||||
Method = definition.Method,
|
||||
Uri = uri,
|
||||
Headers = headers,
|
||||
Body = body,
|
||||
ContainsSecretMaterialInRequest = definition.ContainsSecretMaterialInRequest,
|
||||
ContainsSecretMaterialInResponse = definition.ContainsSecretMaterialInResponse
|
||||
};
|
||||
|
||||
return _httpClient.Send(request);
|
||||
}
|
||||
|
||||
private static InfisicalApiException BuildApiException(InfisicalHttpResponse response, InfisicalEndpointDefinition definition)
|
||||
{
|
||||
InfisicalApiException exception = new InfisicalApiException(string.Concat(
|
||||
"Infisical API returned ",
|
||||
response.StatusCode.ToString(CultureInfo.InvariantCulture),
|
||||
" (", response.ReasonPhrase ?? string.Empty, ")."));
|
||||
exception.StatusCode = response.StatusCode;
|
||||
exception.ReasonPhrase = response.ReasonPhrase;
|
||||
exception.EndpointName = definition.Name;
|
||||
exception.RequestMethod = definition.Method;
|
||||
exception.SanitizedBody = response.Body;
|
||||
return exception;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,11 @@ namespace PSInfisicalAPI.Models
|
||||
public enum InfisicalAuthType
|
||||
{
|
||||
UniversalAuth,
|
||||
Token
|
||||
Token,
|
||||
Jwt,
|
||||
Oidc,
|
||||
Ldap,
|
||||
Azure,
|
||||
GcpIam
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace PSInfisicalAPI.Models
|
||||
{
|
||||
public sealed class InfisicalEnvironment
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Slug { get; set; }
|
||||
public int? Position { get; set; }
|
||||
public string ProjectId { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.IsNullOrEmpty(Slug) ? (Name ?? Id) : Slug;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
|
||||
namespace PSInfisicalAPI.Models
|
||||
{
|
||||
public sealed class InfisicalFolder
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Path { get; set; }
|
||||
public string ParentId { get; set; }
|
||||
public string Environment { get; set; }
|
||||
public string ProjectId { get; set; }
|
||||
public DateTimeOffset? CreatedAtUtc { get; set; }
|
||||
public DateTimeOffset? UpdatedAtUtc { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.IsNullOrEmpty(Path) ? (Name ?? Id) : Path;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
|
||||
namespace PSInfisicalAPI.Models
|
||||
{
|
||||
public sealed class InfisicalProject
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Slug { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string OrganizationId { get; set; }
|
||||
public string Type { get; set; }
|
||||
public bool AutoCapitalization { get; set; }
|
||||
public string[] EnvironmentSlugs { get; set; }
|
||||
public DateTimeOffset? CreatedAtUtc { get; set; }
|
||||
public DateTimeOffset? UpdatedAtUtc { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.IsNullOrEmpty(Slug) ? (Name ?? Id) : Slug;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
|
||||
namespace PSInfisicalAPI.Models
|
||||
{
|
||||
public sealed class InfisicalTag
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Slug { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Color { get; set; }
|
||||
public string ProjectId { get; set; }
|
||||
public DateTimeOffset? CreatedAtUtc { get; set; }
|
||||
public DateTimeOffset? UpdatedAtUtc { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Slug ?? Name ?? Id;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using PSInfisicalAPI.Connections;
|
||||
using PSInfisicalAPI.Endpoints;
|
||||
using PSInfisicalAPI.Errors;
|
||||
using PSInfisicalAPI.Http;
|
||||
using PSInfisicalAPI.Logging;
|
||||
using PSInfisicalAPI.Models;
|
||||
using PSInfisicalAPI.Serialization;
|
||||
|
||||
namespace PSInfisicalAPI.Projects
|
||||
{
|
||||
public sealed class InfisicalProjectClient
|
||||
{
|
||||
private const string Component = "ProjectClient";
|
||||
|
||||
private readonly IInfisicalLogger _logger;
|
||||
private readonly JsonInfisicalSerializer _serializer;
|
||||
private readonly InfisicalApiInvoker _invoker;
|
||||
|
||||
public InfisicalProjectClient(IInfisicalHttpClient httpClient, IInfisicalLogger logger)
|
||||
{
|
||||
if (httpClient == null) { throw new ArgumentNullException(nameof(httpClient)); }
|
||||
_logger = logger ?? NullInfisicalLogger.Instance;
|
||||
_serializer = new JsonInfisicalSerializer();
|
||||
_invoker = new InfisicalApiInvoker(httpClient);
|
||||
}
|
||||
|
||||
public InfisicalProject[] List(InfisicalConnection connection)
|
||||
{
|
||||
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Information(Component, "Attempting to list Infisical projects. Please Wait...");
|
||||
InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.ListProjects, "ListProjects", null, null, null);
|
||||
InfisicalProjectListResponseDto dto = _serializer.Deserialize<InfisicalProjectListResponseDto>(response.Body);
|
||||
response.Clear();
|
||||
|
||||
List<InfisicalProjectResponseDto> source = (dto != null && dto.Workspaces != null) ? dto.Workspaces : (dto != null ? dto.Projects : null);
|
||||
InfisicalProject[] mapped = InfisicalProjectMapper.MapMany(source);
|
||||
_logger.Information(Component, "Infisical project list retrieval was successful.");
|
||||
return mapped;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_logger.Error(Component, "Infisical project list retrieval failed.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public InfisicalProject Retrieve(InfisicalConnection connection, string projectId)
|
||||
{
|
||||
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
|
||||
if (string.IsNullOrEmpty(projectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
|
||||
|
||||
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "projectId", projectId } };
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Information(Component, string.Concat("Attempting to retrieve Infisical project '", projectId, "'. Please Wait..."));
|
||||
InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.RetrieveProject, "RetrieveProject", pathParameters, null, null);
|
||||
InfisicalProjectSingleResponseDto dto = _serializer.Deserialize<InfisicalProjectSingleResponseDto>(response.Body);
|
||||
response.Clear();
|
||||
|
||||
InfisicalProjectResponseDto inner = dto != null ? (dto.Workspace ?? dto.Project) : null;
|
||||
InfisicalProject mapped = InfisicalProjectMapper.Map(inner);
|
||||
_logger.Information(Component, "Infisical project retrieval was successful.");
|
||||
return mapped;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_logger.Error(Component, "Infisical project retrieval failed.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public InfisicalProject Create(InfisicalConnection connection, string projectName, string slug, string description, string type, string organizationId)
|
||||
{
|
||||
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
|
||||
if (string.IsNullOrEmpty(projectName)) { throw new InfisicalConfigurationException("ProjectName is required."); }
|
||||
|
||||
InfisicalProjectCreateRequestDto request = new InfisicalProjectCreateRequestDto
|
||||
{
|
||||
ProjectName = projectName,
|
||||
Slug = slug,
|
||||
ProjectDescription = description,
|
||||
Type = type,
|
||||
OrganizationId = organizationId
|
||||
};
|
||||
|
||||
string body = _serializer.Serialize(request);
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Information(Component, string.Concat("Attempting to create Infisical project '", projectName, "'. Please Wait..."));
|
||||
InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.CreateProject, "CreateProject", null, null, body);
|
||||
InfisicalProjectSingleResponseDto dto = _serializer.Deserialize<InfisicalProjectSingleResponseDto>(response.Body);
|
||||
response.Clear();
|
||||
|
||||
InfisicalProjectResponseDto inner = dto != null ? (dto.Project ?? dto.Workspace) : null;
|
||||
InfisicalProject mapped = InfisicalProjectMapper.Map(inner);
|
||||
_logger.Information(Component, "Infisical project creation was successful.");
|
||||
return mapped;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_logger.Error(Component, "Infisical project creation failed.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public InfisicalProject Update(InfisicalConnection connection, string projectId, string name, string description, bool? autoCapitalization)
|
||||
{
|
||||
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
|
||||
if (string.IsNullOrEmpty(projectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
|
||||
|
||||
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "projectId", projectId } };
|
||||
InfisicalProjectUpdateRequestDto request = new InfisicalProjectUpdateRequestDto { Name = name, Description = description, AutoCapitalization = autoCapitalization };
|
||||
string body = _serializer.Serialize(request);
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Information(Component, string.Concat("Attempting to update Infisical project '", projectId, "'. Please Wait..."));
|
||||
InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.UpdateProject, "UpdateProject", pathParameters, null, body);
|
||||
InfisicalProjectSingleResponseDto dto = _serializer.Deserialize<InfisicalProjectSingleResponseDto>(response.Body);
|
||||
response.Clear();
|
||||
|
||||
InfisicalProjectResponseDto inner = dto != null ? (dto.Workspace ?? dto.Project) : null;
|
||||
InfisicalProject mapped = InfisicalProjectMapper.Map(inner);
|
||||
_logger.Information(Component, "Infisical project update was successful.");
|
||||
return mapped;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_logger.Error(Component, "Infisical project update failed.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public void Delete(InfisicalConnection connection, string projectId)
|
||||
{
|
||||
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
|
||||
if (string.IsNullOrEmpty(projectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
|
||||
|
||||
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "projectId", projectId } };
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Information(Component, string.Concat("Attempting to delete Infisical project '", projectId, "'. Please Wait..."));
|
||||
InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.DeleteProject, "DeleteProject", pathParameters, null, null);
|
||||
response.Clear();
|
||||
_logger.Information(Component, "Infisical project deletion was successful.");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_logger.Error(Component, "Infisical project deletion failed.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PSInfisicalAPI.Projects
|
||||
{
|
||||
internal sealed class InfisicalProjectResponseDto
|
||||
{
|
||||
[JsonProperty("id")] public string Id { get; set; }
|
||||
[JsonProperty("_id")] public string InternalId { get; set; }
|
||||
[JsonProperty("name")] public string Name { get; set; }
|
||||
[JsonProperty("slug")] public string Slug { get; set; }
|
||||
[JsonProperty("description")] public string Description { get; set; }
|
||||
[JsonProperty("organization")] public string Organization { get; set; }
|
||||
[JsonProperty("orgId")] public string OrgId { get; set; }
|
||||
[JsonProperty("type")] public string Type { get; set; }
|
||||
[JsonProperty("autoCapitalization")] public bool AutoCapitalization { get; set; }
|
||||
[JsonProperty("createdAt")] public string CreatedAt { get; set; }
|
||||
[JsonProperty("updatedAt")] public string UpdatedAt { get; set; }
|
||||
[JsonProperty("environments")] public List<InfisicalProjectEnvironmentDto> Environments { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class InfisicalProjectEnvironmentDto
|
||||
{
|
||||
[JsonProperty("id")] public string Id { get; set; }
|
||||
[JsonProperty("name")] public string Name { get; set; }
|
||||
[JsonProperty("slug")] public string Slug { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class InfisicalProjectListResponseDto
|
||||
{
|
||||
[JsonProperty("workspaces")] public List<InfisicalProjectResponseDto> Workspaces { get; set; }
|
||||
[JsonProperty("projects")] public List<InfisicalProjectResponseDto> Projects { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class InfisicalProjectSingleResponseDto
|
||||
{
|
||||
[JsonProperty("workspace")] public InfisicalProjectResponseDto Workspace { get; set; }
|
||||
[JsonProperty("project")] public InfisicalProjectResponseDto Project { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class InfisicalProjectCreateRequestDto
|
||||
{
|
||||
[JsonProperty("projectName")] public string ProjectName { get; set; }
|
||||
[JsonProperty("slug", NullValueHandling = NullValueHandling.Ignore)] public string Slug { get; set; }
|
||||
[JsonProperty("projectDescription", NullValueHandling = NullValueHandling.Ignore)] public string ProjectDescription { get; set; }
|
||||
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)] public string Type { get; set; }
|
||||
[JsonProperty("organizationId", NullValueHandling = NullValueHandling.Ignore)] public string OrganizationId { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class InfisicalProjectUpdateRequestDto
|
||||
{
|
||||
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] public string Name { get; set; }
|
||||
[JsonProperty("description", NullValueHandling = NullValueHandling.Ignore)] public string Description { get; set; }
|
||||
[JsonProperty("autoCapitalization", NullValueHandling = NullValueHandling.Ignore)] public bool? AutoCapitalization { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using PSInfisicalAPI.Models;
|
||||
|
||||
namespace PSInfisicalAPI.Projects
|
||||
{
|
||||
internal static class InfisicalProjectMapper
|
||||
{
|
||||
public static InfisicalProject Map(InfisicalProjectResponseDto dto)
|
||||
{
|
||||
if (dto == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
InfisicalProject project = new InfisicalProject
|
||||
{
|
||||
Id = !string.IsNullOrEmpty(dto.Id) ? dto.Id : dto.InternalId,
|
||||
Name = dto.Name,
|
||||
Slug = dto.Slug,
|
||||
Description = dto.Description,
|
||||
OrganizationId = !string.IsNullOrEmpty(dto.Organization) ? dto.Organization : dto.OrgId,
|
||||
Type = dto.Type,
|
||||
AutoCapitalization = dto.AutoCapitalization,
|
||||
EnvironmentSlugs = MapEnvironmentSlugs(dto.Environments),
|
||||
CreatedAtUtc = ParseTimestamp(dto.CreatedAt),
|
||||
UpdatedAtUtc = ParseTimestamp(dto.UpdatedAt)
|
||||
};
|
||||
|
||||
return project;
|
||||
}
|
||||
|
||||
public static InfisicalProject[] MapMany(IEnumerable<InfisicalProjectResponseDto> items)
|
||||
{
|
||||
if (items == null)
|
||||
{
|
||||
return Array.Empty<InfisicalProject>();
|
||||
}
|
||||
|
||||
List<InfisicalProject> results = new List<InfisicalProject>();
|
||||
foreach (InfisicalProjectResponseDto dto in items)
|
||||
{
|
||||
InfisicalProject mapped = Map(dto);
|
||||
if (mapped != null)
|
||||
{
|
||||
results.Add(mapped);
|
||||
}
|
||||
}
|
||||
|
||||
return results.ToArray();
|
||||
}
|
||||
|
||||
private static string[] MapEnvironmentSlugs(List<InfisicalProjectEnvironmentDto> environments)
|
||||
{
|
||||
if (environments == null || environments.Count == 0)
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
List<string> slugs = new List<string>(environments.Count);
|
||||
foreach (InfisicalProjectEnvironmentDto env in environments)
|
||||
{
|
||||
if (env != null && !string.IsNullOrEmpty(env.Slug))
|
||||
{
|
||||
slugs.Add(env.Slug);
|
||||
}
|
||||
}
|
||||
|
||||
return slugs.ToArray();
|
||||
}
|
||||
|
||||
private static DateTimeOffset? ParseTimestamp(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
DateTimeOffset parsed;
|
||||
if (DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out parsed))
|
||||
{
|
||||
return parsed;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using PSInfisicalAPI.Errors;
|
||||
|
||||
namespace PSInfisicalAPI.Secrets
|
||||
{
|
||||
public static class InfisicalBulkSecretConverter
|
||||
{
|
||||
public static InfisicalBulkCreateSecretItem[] ToCreateItems(IEnumerable<IDictionary<string, string>> input)
|
||||
{
|
||||
if (input == null) { return new InfisicalBulkCreateSecretItem[0]; }
|
||||
|
||||
List<InfisicalBulkCreateSecretItem> list = new List<InfisicalBulkCreateSecretItem>();
|
||||
foreach (IDictionary<string, string> entry in input)
|
||||
{
|
||||
if (entry == null) { continue; }
|
||||
IDictionary<string, string> table = Normalize(entry);
|
||||
InfisicalBulkCreateSecretItem item = new InfisicalBulkCreateSecretItem
|
||||
{
|
||||
SecretName = GetString(table, "SecretName", "Name", "Key", "SecretKey"),
|
||||
SecretValue = GetString(table, "SecretValue", "Value"),
|
||||
SecretComment = GetString(table, "SecretComment", "Comment"),
|
||||
SkipMultilineEncoding = GetBool(table, "SkipMultilineEncoding"),
|
||||
TagIds = GetStringArray(table, "TagIds")
|
||||
};
|
||||
|
||||
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<IDictionary<string, string>> input)
|
||||
{
|
||||
if (input == null) { return new InfisicalBulkUpdateSecretItem[0]; }
|
||||
|
||||
List<InfisicalBulkUpdateSecretItem> list = new List<InfisicalBulkUpdateSecretItem>();
|
||||
foreach (IDictionary<string, string> entry in input)
|
||||
{
|
||||
if (entry == null) { continue; }
|
||||
IDictionary<string, string> table = Normalize(entry);
|
||||
InfisicalBulkUpdateSecretItem item = new InfisicalBulkUpdateSecretItem
|
||||
{
|
||||
SecretName = GetString(table, "SecretName", "Name", "Key", "SecretKey"),
|
||||
NewSecretName = GetString(table, "NewSecretName", "NewName"),
|
||||
SecretValue = GetString(table, "SecretValue", "Value"),
|
||||
SecretComment = GetString(table, "SecretComment", "Comment"),
|
||||
SkipMultilineEncoding = GetBool(table, "SkipMultilineEncoding"),
|
||||
TagIds = GetStringArray(table, "TagIds")
|
||||
};
|
||||
|
||||
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 IDictionary<string, string> Normalize(IDictionary<string, string> source)
|
||||
{
|
||||
Dictionary<string, string> normalized = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (KeyValuePair<string, string> kvp in source)
|
||||
{
|
||||
if (string.IsNullOrEmpty(kvp.Key)) { continue; }
|
||||
normalized[kvp.Key] = kvp.Value;
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private static string GetString(IDictionary<string, string> table, params string[] keys)
|
||||
{
|
||||
foreach (string key in keys)
|
||||
{
|
||||
string value;
|
||||
if (table.TryGetValue(key, out value) && !string.IsNullOrEmpty(value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool? GetBool(IDictionary<string, string> table, string key)
|
||||
{
|
||||
string value;
|
||||
if (!table.TryGetValue(key, out value) || string.IsNullOrEmpty(value)) { return null; }
|
||||
bool parsed;
|
||||
return bool.TryParse(value, out parsed) ? parsed : (bool?)null;
|
||||
}
|
||||
|
||||
private static string[] GetStringArray(IDictionary<string, string> table, string key)
|
||||
{
|
||||
string value;
|
||||
if (!table.TryGetValue(key, out value) || string.IsNullOrEmpty(value)) { return null; }
|
||||
string[] parts = value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
List<string> trimmed = new List<string>(parts.Length);
|
||||
foreach (string part in parts)
|
||||
{
|
||||
string item = part.Trim();
|
||||
if (!string.IsNullOrEmpty(item)) { trimmed.Add(item); }
|
||||
}
|
||||
|
||||
return trimmed.Count == 0 ? null : trimmed.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,4 +55,111 @@ namespace PSInfisicalAPI.Secrets
|
||||
{
|
||||
[JsonProperty("secret")] public InfisicalSecretResponseDto Secret { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class InfisicalSecretCreateRequestDto
|
||||
{
|
||||
[JsonProperty("workspaceId")] public string WorkspaceId { get; set; }
|
||||
[JsonProperty("environment")] public string Environment { get; set; }
|
||||
[JsonProperty("secretPath", NullValueHandling = NullValueHandling.Ignore)] public string SecretPath { get; set; }
|
||||
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)] public string Type { get; set; }
|
||||
[JsonProperty("secretValue")] public string SecretValue { get; set; }
|
||||
[JsonProperty("secretComment", NullValueHandling = NullValueHandling.Ignore)] public string SecretComment { get; set; }
|
||||
[JsonProperty("skipMultilineEncoding", NullValueHandling = NullValueHandling.Ignore)] public bool? SkipMultilineEncoding { get; set; }
|
||||
[JsonProperty("tagIds", NullValueHandling = NullValueHandling.Ignore)] public string[] TagIds { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class InfisicalSecretUpdateRequestDto
|
||||
{
|
||||
[JsonProperty("workspaceId")] public string WorkspaceId { get; set; }
|
||||
[JsonProperty("environment")] public string Environment { get; set; }
|
||||
[JsonProperty("secretPath", NullValueHandling = NullValueHandling.Ignore)] public string SecretPath { get; set; }
|
||||
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)] public string Type { get; set; }
|
||||
[JsonProperty("secretValue", NullValueHandling = NullValueHandling.Ignore)] public string SecretValue { get; set; }
|
||||
[JsonProperty("secretComment", NullValueHandling = NullValueHandling.Ignore)] public string SecretComment { get; set; }
|
||||
[JsonProperty("newSecretName", NullValueHandling = NullValueHandling.Ignore)] public string NewSecretName { get; set; }
|
||||
[JsonProperty("skipMultilineEncoding", NullValueHandling = NullValueHandling.Ignore)] public bool? SkipMultilineEncoding { get; set; }
|
||||
[JsonProperty("tagIds", NullValueHandling = NullValueHandling.Ignore)] public string[] TagIds { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class InfisicalSecretDeleteRequestDto
|
||||
{
|
||||
[JsonProperty("workspaceId")] public string WorkspaceId { get; set; }
|
||||
[JsonProperty("environment")] public string Environment { get; set; }
|
||||
[JsonProperty("secretPath", NullValueHandling = NullValueHandling.Ignore)] public string SecretPath { get; set; }
|
||||
[JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)] public string Type { get; set; }
|
||||
}
|
||||
|
||||
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", 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<InfisicalSecretBatchCreateItemDto> Secrets { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class InfisicalSecretBatchUpdateRequestDto
|
||||
{
|
||||
[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; }
|
||||
[JsonProperty("secrets")] public List<InfisicalSecretBatchUpdateItemDto> Secrets { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class InfisicalSecretBatchDeleteRequestDto
|
||||
{
|
||||
[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<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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,4 +30,108 @@ namespace PSInfisicalAPI.Secrets
|
||||
public bool? ExpandSecretReferences { get; set; }
|
||||
public bool? IncludeImports { get; set; }
|
||||
}
|
||||
|
||||
public sealed class InfisicalCreateSecretRequest
|
||||
{
|
||||
public string SecretName { get; set; }
|
||||
public string SecretValue { get; set; }
|
||||
public string SecretComment { get; set; }
|
||||
public string ProjectId { get; set; }
|
||||
public string Environment { get; set; }
|
||||
public string SecretPath { get; set; }
|
||||
public string Type { get; set; }
|
||||
public string ApiVersion { get; set; }
|
||||
public bool? SkipMultilineEncoding { get; set; }
|
||||
public string[] TagIds { get; set; }
|
||||
}
|
||||
|
||||
public sealed class InfisicalUpdateSecretRequest
|
||||
{
|
||||
public string SecretName { get; set; }
|
||||
public string NewSecretName { get; set; }
|
||||
public string SecretValue { get; set; }
|
||||
public string SecretComment { get; set; }
|
||||
public string ProjectId { get; set; }
|
||||
public string Environment { get; set; }
|
||||
public string SecretPath { get; set; }
|
||||
public string Type { get; set; }
|
||||
public string ApiVersion { get; set; }
|
||||
public bool? SkipMultilineEncoding { get; set; }
|
||||
public string[] TagIds { get; set; }
|
||||
}
|
||||
|
||||
public sealed class InfisicalDeleteSecretRequest
|
||||
{
|
||||
public string SecretName { get; set; }
|
||||
public string ProjectId { get; set; }
|
||||
public string Environment { get; set; }
|
||||
public string SecretPath { get; set; }
|
||||
public string Type { get; set; }
|
||||
public string ApiVersion { get; set; }
|
||||
}
|
||||
|
||||
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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,6 +136,353 @@ namespace PSInfisicalAPI.Secrets
|
||||
}
|
||||
}
|
||||
|
||||
public InfisicalSecret Create(InfisicalConnection connection, InfisicalCreateSecretRequest request)
|
||||
{
|
||||
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
|
||||
if (request == null) { throw new ArgumentNullException(nameof(request)); }
|
||||
if (string.IsNullOrEmpty(request.SecretName)) { throw new InfisicalConfigurationException("SecretName is required."); }
|
||||
if (request.SecretValue == null) { throw new InfisicalConfigurationException("SecretValue is required."); }
|
||||
|
||||
string resolvedProjectId = FirstNonEmpty(request.ProjectId, connection.ProjectId);
|
||||
string resolvedEnvironment = FirstNonEmpty(request.Environment, connection.Environment);
|
||||
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
|
||||
if (string.IsNullOrEmpty(resolvedEnvironment)) { throw new InfisicalConfigurationException("Environment is required."); }
|
||||
|
||||
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "secretName", request.SecretName } };
|
||||
InfisicalSecretCreateRequestDto dtoRequest = new InfisicalSecretCreateRequestDto
|
||||
{
|
||||
WorkspaceId = resolvedProjectId,
|
||||
Environment = resolvedEnvironment,
|
||||
SecretPath = FirstNonEmpty(request.SecretPath, connection.DefaultSecretPath, "/"),
|
||||
Type = string.IsNullOrEmpty(request.Type) ? "shared" : request.Type.ToLowerInvariant(),
|
||||
SecretValue = request.SecretValue,
|
||||
SecretComment = request.SecretComment,
|
||||
SkipMultilineEncoding = request.SkipMultilineEncoding,
|
||||
TagIds = request.TagIds
|
||||
};
|
||||
string body = _serializer.Serialize(dtoRequest);
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Information(Component, string.Concat("Attempting to create Infisical secret '", request.SecretName, "'. Please Wait..."));
|
||||
InfisicalHttpResponse response = SendWithVersionFallback(connection, InfisicalEndpointNames.CreateSecret, request.ApiVersion, "CreateSecret", pathParameters, null, body);
|
||||
InfisicalSecretSingleResponseDto dto = _serializer.Deserialize<InfisicalSecretSingleResponseDto>(response.Body);
|
||||
response.Clear();
|
||||
|
||||
InfisicalSecret mapped = InfisicalSecretMapper.Map(dto != null ? dto.Secret : null);
|
||||
_logger.Information(Component, "Infisical secret creation was successful.");
|
||||
return mapped;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_logger.Error(Component, "Infisical secret creation failed.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public InfisicalSecret Update(InfisicalConnection connection, InfisicalUpdateSecretRequest request)
|
||||
{
|
||||
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
|
||||
if (request == null) { throw new ArgumentNullException(nameof(request)); }
|
||||
if (string.IsNullOrEmpty(request.SecretName)) { throw new InfisicalConfigurationException("SecretName is required."); }
|
||||
|
||||
string resolvedProjectId = FirstNonEmpty(request.ProjectId, connection.ProjectId);
|
||||
string resolvedEnvironment = FirstNonEmpty(request.Environment, connection.Environment);
|
||||
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
|
||||
if (string.IsNullOrEmpty(resolvedEnvironment)) { throw new InfisicalConfigurationException("Environment is required."); }
|
||||
|
||||
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "secretName", request.SecretName } };
|
||||
InfisicalSecretUpdateRequestDto dtoRequest = new InfisicalSecretUpdateRequestDto
|
||||
{
|
||||
WorkspaceId = resolvedProjectId,
|
||||
Environment = resolvedEnvironment,
|
||||
SecretPath = FirstNonEmpty(request.SecretPath, connection.DefaultSecretPath, "/"),
|
||||
Type = string.IsNullOrEmpty(request.Type) ? "shared" : request.Type.ToLowerInvariant(),
|
||||
SecretValue = request.SecretValue,
|
||||
SecretComment = request.SecretComment,
|
||||
NewSecretName = request.NewSecretName,
|
||||
SkipMultilineEncoding = request.SkipMultilineEncoding,
|
||||
TagIds = request.TagIds
|
||||
};
|
||||
string body = _serializer.Serialize(dtoRequest);
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Information(Component, string.Concat("Attempting to update Infisical secret '", request.SecretName, "'. Please Wait..."));
|
||||
InfisicalHttpResponse response = SendWithVersionFallback(connection, InfisicalEndpointNames.UpdateSecret, request.ApiVersion, "UpdateSecret", pathParameters, null, body);
|
||||
InfisicalSecretSingleResponseDto dto = _serializer.Deserialize<InfisicalSecretSingleResponseDto>(response.Body);
|
||||
response.Clear();
|
||||
|
||||
InfisicalSecret mapped = InfisicalSecretMapper.Map(dto != null ? dto.Secret : null);
|
||||
_logger.Information(Component, "Infisical secret update was successful.");
|
||||
return mapped;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_logger.Error(Component, "Infisical secret update failed.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public 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,
|
||||
ProjectId = 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,
|
||||
ProjectId = 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,
|
||||
ProjectId = 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)
|
||||
{
|
||||
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
|
||||
if (request == null) { throw new ArgumentNullException(nameof(request)); }
|
||||
if (string.IsNullOrEmpty(request.SecretName)) { throw new InfisicalConfigurationException("SecretName is required."); }
|
||||
|
||||
string resolvedProjectId = FirstNonEmpty(request.ProjectId, connection.ProjectId);
|
||||
string resolvedEnvironment = FirstNonEmpty(request.Environment, connection.Environment);
|
||||
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
|
||||
if (string.IsNullOrEmpty(resolvedEnvironment)) { throw new InfisicalConfigurationException("Environment is required."); }
|
||||
|
||||
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "secretName", request.SecretName } };
|
||||
InfisicalSecretDeleteRequestDto dtoRequest = new InfisicalSecretDeleteRequestDto
|
||||
{
|
||||
WorkspaceId = resolvedProjectId,
|
||||
Environment = resolvedEnvironment,
|
||||
SecretPath = FirstNonEmpty(request.SecretPath, connection.DefaultSecretPath, "/"),
|
||||
Type = string.IsNullOrEmpty(request.Type) ? "shared" : request.Type.ToLowerInvariant()
|
||||
};
|
||||
string body = _serializer.Serialize(dtoRequest);
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Information(Component, string.Concat("Attempting to delete Infisical secret '", request.SecretName, "'. Please Wait..."));
|
||||
InfisicalHttpResponse response = SendWithVersionFallback(connection, InfisicalEndpointNames.DeleteSecret, request.ApiVersion, "DeleteSecret", pathParameters, null, body);
|
||||
response.Clear();
|
||||
_logger.Information(Component, "Infisical secret deletion was successful.");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_logger.Error(Component, "Infisical secret deletion failed.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private InfisicalHttpResponse SendWithVersionFallback(
|
||||
InfisicalConnection connection,
|
||||
string endpointName,
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using PSInfisicalAPI.Connections;
|
||||
using PSInfisicalAPI.Endpoints;
|
||||
using PSInfisicalAPI.Errors;
|
||||
using PSInfisicalAPI.Http;
|
||||
using PSInfisicalAPI.Logging;
|
||||
using PSInfisicalAPI.Models;
|
||||
using PSInfisicalAPI.Serialization;
|
||||
|
||||
namespace PSInfisicalAPI.Tags
|
||||
{
|
||||
public sealed class InfisicalTagClient
|
||||
{
|
||||
private const string Component = "TagClient";
|
||||
|
||||
private readonly IInfisicalLogger _logger;
|
||||
private readonly JsonInfisicalSerializer _serializer;
|
||||
private readonly InfisicalApiInvoker _invoker;
|
||||
|
||||
public InfisicalTagClient(IInfisicalHttpClient httpClient, IInfisicalLogger logger)
|
||||
{
|
||||
if (httpClient == null) { throw new ArgumentNullException(nameof(httpClient)); }
|
||||
_logger = logger ?? NullInfisicalLogger.Instance;
|
||||
_serializer = new JsonInfisicalSerializer();
|
||||
_invoker = new InfisicalApiInvoker(httpClient);
|
||||
}
|
||||
|
||||
public InfisicalTag[] List(InfisicalConnection connection, string projectId)
|
||||
{
|
||||
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
|
||||
string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId);
|
||||
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
|
||||
|
||||
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "projectId", resolvedProjectId } };
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Information(Component, "Attempting to list Infisical tags. Please Wait...");
|
||||
InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.ListTags, "ListTags", pathParameters, null, null);
|
||||
InfisicalTagListResponseDto dto = _serializer.Deserialize<InfisicalTagListResponseDto>(response.Body);
|
||||
response.Clear();
|
||||
|
||||
List<InfisicalTagResponseDto> source = dto != null ? (dto.WorkspaceTags ?? dto.Tags) : null;
|
||||
InfisicalTag[] mapped = InfisicalTagMapper.MapMany(source, resolvedProjectId);
|
||||
_logger.Information(Component, "Infisical tag list retrieval was successful.");
|
||||
return mapped;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_logger.Error(Component, "Infisical tag list retrieval failed.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public InfisicalTag Retrieve(InfisicalConnection connection, string projectId, string tagSlugOrId)
|
||||
{
|
||||
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
|
||||
string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId);
|
||||
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
|
||||
if (string.IsNullOrEmpty(tagSlugOrId)) { throw new InfisicalConfigurationException("Tag slug or id is required."); }
|
||||
|
||||
InfisicalTag[] all = List(connection, resolvedProjectId);
|
||||
foreach (InfisicalTag tag in all)
|
||||
{
|
||||
if (string.Equals(tag.Id, tagSlugOrId, StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(tag.Slug, tagSlugOrId, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return tag;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public InfisicalTag Create(InfisicalConnection connection, string projectId, string slug, string name, string color)
|
||||
{
|
||||
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
|
||||
string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId);
|
||||
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
|
||||
if (string.IsNullOrEmpty(slug)) { throw new InfisicalConfigurationException("Slug is required."); }
|
||||
|
||||
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "projectId", resolvedProjectId } };
|
||||
InfisicalTagCreateRequestDto request = new InfisicalTagCreateRequestDto { Slug = slug, Name = name, Color = color };
|
||||
string body = _serializer.Serialize(request);
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Information(Component, string.Concat("Attempting to create Infisical tag '", slug, "'. Please Wait..."));
|
||||
InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.CreateTag, "CreateTag", pathParameters, null, body);
|
||||
InfisicalTagSingleResponseDto dto = _serializer.Deserialize<InfisicalTagSingleResponseDto>(response.Body);
|
||||
response.Clear();
|
||||
|
||||
InfisicalTagResponseDto inner = dto != null ? (dto.WorkspaceTag ?? dto.Tag) : null;
|
||||
InfisicalTag mapped = InfisicalTagMapper.Map(inner, resolvedProjectId);
|
||||
_logger.Information(Component, "Infisical tag creation was successful.");
|
||||
return mapped;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_logger.Error(Component, "Infisical tag creation failed.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public InfisicalTag Update(InfisicalConnection connection, string projectId, string tagId, string slug, string name, string color)
|
||||
{
|
||||
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
|
||||
string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId);
|
||||
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
|
||||
if (string.IsNullOrEmpty(tagId)) { throw new InfisicalConfigurationException("TagId is required."); }
|
||||
|
||||
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "projectId", resolvedProjectId }, { "tagId", tagId } };
|
||||
InfisicalTagUpdateRequestDto request = new InfisicalTagUpdateRequestDto { Slug = slug, Name = name, Color = color };
|
||||
string body = _serializer.Serialize(request);
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Information(Component, string.Concat("Attempting to update Infisical tag '", tagId, "'. Please Wait..."));
|
||||
InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.UpdateTag, "UpdateTag", pathParameters, null, body);
|
||||
InfisicalTagSingleResponseDto dto = _serializer.Deserialize<InfisicalTagSingleResponseDto>(response.Body);
|
||||
response.Clear();
|
||||
|
||||
InfisicalTagResponseDto inner = dto != null ? (dto.WorkspaceTag ?? dto.Tag) : null;
|
||||
InfisicalTag mapped = InfisicalTagMapper.Map(inner, resolvedProjectId);
|
||||
_logger.Information(Component, "Infisical tag update was successful.");
|
||||
return mapped;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_logger.Error(Component, "Infisical tag update failed.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public void Delete(InfisicalConnection connection, string projectId, string tagId)
|
||||
{
|
||||
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
|
||||
string resolvedProjectId = FirstNonEmpty(projectId, connection.ProjectId);
|
||||
if (string.IsNullOrEmpty(resolvedProjectId)) { throw new InfisicalConfigurationException("ProjectId is required."); }
|
||||
if (string.IsNullOrEmpty(tagId)) { throw new InfisicalConfigurationException("TagId is required."); }
|
||||
|
||||
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "projectId", resolvedProjectId }, { "tagId", tagId } };
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Information(Component, string.Concat("Attempting to delete Infisical tag '", tagId, "'. Please Wait..."));
|
||||
InfisicalHttpResponse response = _invoker.Invoke(connection, InfisicalEndpointNames.DeleteTag, "DeleteTag", pathParameters, null, null);
|
||||
response.Clear();
|
||||
_logger.Information(Component, "Infisical tag deletion was successful.");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_logger.Error(Component, "Infisical tag deletion failed.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static string FirstNonEmpty(params string[] values)
|
||||
{
|
||||
if (values == null) { return null; }
|
||||
foreach (string value in values) { if (!string.IsNullOrEmpty(value)) { return value; } }
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PSInfisicalAPI.Tags
|
||||
{
|
||||
internal sealed class InfisicalTagResponseDto
|
||||
{
|
||||
[JsonProperty("id")] public string Id { get; set; }
|
||||
[JsonProperty("_id")] public string InternalId { get; set; }
|
||||
[JsonProperty("slug")] public string Slug { get; set; }
|
||||
[JsonProperty("name")] public string Name { get; set; }
|
||||
[JsonProperty("color")] public string Color { get; set; }
|
||||
[JsonProperty("projectId")] public string ProjectId { get; set; }
|
||||
[JsonProperty("workspaceId")] public string WorkspaceId { get; set; }
|
||||
[JsonProperty("createdAt")] public string CreatedAt { get; set; }
|
||||
[JsonProperty("updatedAt")] public string UpdatedAt { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class InfisicalTagListResponseDto
|
||||
{
|
||||
[JsonProperty("workspaceTags")] public List<InfisicalTagResponseDto> WorkspaceTags { get; set; }
|
||||
[JsonProperty("tags")] public List<InfisicalTagResponseDto> Tags { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class InfisicalTagSingleResponseDto
|
||||
{
|
||||
[JsonProperty("workspaceTag")] public InfisicalTagResponseDto WorkspaceTag { get; set; }
|
||||
[JsonProperty("tag")] public InfisicalTagResponseDto Tag { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class InfisicalTagCreateRequestDto
|
||||
{
|
||||
[JsonProperty("slug")] public string Slug { get; set; }
|
||||
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] public string Name { get; set; }
|
||||
[JsonProperty("color", NullValueHandling = NullValueHandling.Ignore)] public string Color { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class InfisicalTagUpdateRequestDto
|
||||
{
|
||||
[JsonProperty("slug", NullValueHandling = NullValueHandling.Ignore)] public string Slug { get; set; }
|
||||
[JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] public string Name { get; set; }
|
||||
[JsonProperty("color", NullValueHandling = NullValueHandling.Ignore)] public string Color { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using PSInfisicalAPI.Models;
|
||||
|
||||
namespace PSInfisicalAPI.Tags
|
||||
{
|
||||
internal static class InfisicalTagMapper
|
||||
{
|
||||
public static InfisicalTag Map(InfisicalTagResponseDto dto, string fallbackProjectId)
|
||||
{
|
||||
if (dto == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string projectId = !string.IsNullOrEmpty(dto.ProjectId)
|
||||
? dto.ProjectId
|
||||
: (!string.IsNullOrEmpty(dto.WorkspaceId) ? dto.WorkspaceId : fallbackProjectId);
|
||||
|
||||
return new InfisicalTag
|
||||
{
|
||||
Id = !string.IsNullOrEmpty(dto.Id) ? dto.Id : dto.InternalId,
|
||||
Slug = dto.Slug,
|
||||
Name = dto.Name,
|
||||
Color = dto.Color,
|
||||
ProjectId = projectId,
|
||||
CreatedAtUtc = ParseTimestamp(dto.CreatedAt),
|
||||
UpdatedAtUtc = ParseTimestamp(dto.UpdatedAt)
|
||||
};
|
||||
}
|
||||
|
||||
public static InfisicalTag[] MapMany(IEnumerable<InfisicalTagResponseDto> items, string fallbackProjectId)
|
||||
{
|
||||
if (items == null)
|
||||
{
|
||||
return Array.Empty<InfisicalTag>();
|
||||
}
|
||||
|
||||
List<InfisicalTag> results = new List<InfisicalTag>();
|
||||
foreach (InfisicalTagResponseDto dto in items)
|
||||
{
|
||||
InfisicalTag mapped = Map(dto, fallbackProjectId);
|
||||
if (mapped != null)
|
||||
{
|
||||
results.Add(mapped);
|
||||
}
|
||||
}
|
||||
|
||||
return results.ToArray();
|
||||
}
|
||||
|
||||
private static DateTimeOffset? ParseTimestamp(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
DateTimeOffset parsed;
|
||||
if (DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out parsed))
|
||||
{
|
||||
return parsed;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user