Rebrand to Grace Solutions; add README, about_ help, Gitea CI/CD, track Module bin #1
@@ -6,6 +6,36 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) loos
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
## 2026.06.03.0057
|
||||||
|
|
||||||
|
- Build produced from commit 7e5209190ac2.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.03.0056
|
||||||
|
|
||||||
|
- Build produced from commit 7e5209190ac2.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.03.0055
|
||||||
|
|
||||||
|
- Build produced from commit 7e5209190ac2.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.03.0047
|
||||||
|
|
||||||
|
- Build produced from commit 7e5209190ac2.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
|
## 2026.06.03.0046
|
||||||
|
|
||||||
|
- Build produced from commit 7e5209190ac2.
|
||||||
|
|
||||||
|
## Unreleased (carried forward)
|
||||||
|
|
||||||
## 2026.06.03.0032
|
## 2026.06.03.0032
|
||||||
|
|
||||||
- Build produced from commit c86676010532.
|
- Build produced from commit c86676010532.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
@{
|
@{
|
||||||
RootModule = 'PSInfisicalAPI.psm1'
|
RootModule = 'PSInfisicalAPI.psm1'
|
||||||
ModuleVersion = '2026.06.03.0032'
|
ModuleVersion = '2026.06.03.0057'
|
||||||
GUID = 'b8a2f3d4-7c51-4d2f-9e6a-1f0c8b3d4e51'
|
GUID = 'b8a2f3d4-7c51-4d2f-9e6a-1f0c8b3d4e51'
|
||||||
Author = 'Grace Solutions'
|
Author = 'Grace Solutions'
|
||||||
CompanyName = 'Grace Solutions'
|
CompanyName = 'Grace Solutions'
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
LicenseUri = 'https://www.gnu.org/licenses/agpl-3.0.html'
|
LicenseUri = 'https://www.gnu.org/licenses/agpl-3.0.html'
|
||||||
ProjectUri = 'https://prod.git.gracesolution.info/gsadmin/PSInfisicalAPI'
|
ProjectUri = 'https://prod.git.gracesolution.info/gsadmin/PSInfisicalAPI'
|
||||||
ReleaseNotes = 'See CHANGELOG.md in the project repository for release history.'
|
ReleaseNotes = 'See CHANGELOG.md in the project repository for release history.'
|
||||||
CommitHash = 'c86676010532'
|
CommitHash = '7e5209190ac2'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Binary file not shown.
@@ -89,10 +89,13 @@ namespace PSInfisicalAPI.Cmdlets
|
|||||||
throw new InfisicalAuthenticationException("Authentication did not produce an access token.");
|
throw new InfisicalAuthenticationException("Authentication did not produce an access token.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool apiVersionExplicitlyBound = MyInvocation.BoundParameters.ContainsKey("ApiVersion");
|
||||||
|
|
||||||
InfisicalConnection connection = new InfisicalConnection
|
InfisicalConnection connection = new InfisicalConnection
|
||||||
{
|
{
|
||||||
BaseUri = BaseUri,
|
BaseUri = BaseUri,
|
||||||
ApiVersion = ApiVersion,
|
ApiVersion = ApiVersion,
|
||||||
|
PinnedApiVersion = apiVersionExplicitlyBound ? ApiVersion : null,
|
||||||
AuthType = authType,
|
AuthType = authType,
|
||||||
OrganizationId = OrganizationId,
|
OrganizationId = OrganizationId,
|
||||||
ProjectId = ProjectId,
|
ProjectId = ProjectId,
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ namespace PSInfisicalAPI.Cmdlets
|
|||||||
[Parameter] public string ProjectId { get; set; }
|
[Parameter] public string ProjectId { get; set; }
|
||||||
[Parameter] public string Environment { get; set; }
|
[Parameter] public string Environment { get; set; }
|
||||||
[Parameter] public string SecretPath { get; set; }
|
[Parameter] public string SecretPath { get; set; }
|
||||||
|
[Parameter] public string ApiVersion { get; set; }
|
||||||
[Parameter] public int? Version { get; set; }
|
[Parameter] public int? Version { get; set; }
|
||||||
[Parameter] public InfisicalSecretType Type { get; set; } = InfisicalSecretType.Shared;
|
[Parameter] public InfisicalSecretType Type { get; set; } = InfisicalSecretType.Shared;
|
||||||
[Parameter] public SwitchParameter ViewSecretValue { get; set; }
|
[Parameter] public SwitchParameter ViewSecretValue { get; set; }
|
||||||
@@ -34,6 +35,7 @@ namespace PSInfisicalAPI.Cmdlets
|
|||||||
ProjectId = ProjectId,
|
ProjectId = ProjectId,
|
||||||
Environment = Environment,
|
Environment = Environment,
|
||||||
SecretPath = SecretPath,
|
SecretPath = SecretPath,
|
||||||
|
ApiVersion = ApiVersion,
|
||||||
Version = Version,
|
Version = Version,
|
||||||
Type = Type.ToString(),
|
Type = Type.ToString(),
|
||||||
ViewSecretValue = ViewSecretValue.IsPresent,
|
ViewSecretValue = ViewSecretValue.IsPresent,
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ namespace PSInfisicalAPI.Cmdlets
|
|||||||
[Parameter] public string ProjectId { get; set; }
|
[Parameter] public string ProjectId { get; set; }
|
||||||
[Parameter] public string Environment { get; set; }
|
[Parameter] public string Environment { get; set; }
|
||||||
[Parameter] public string SecretPath { get; set; }
|
[Parameter] public string SecretPath { get; set; }
|
||||||
|
[Parameter] public string ApiVersion { get; set; }
|
||||||
[Parameter] public SwitchParameter Recursive { get; set; }
|
[Parameter] public SwitchParameter Recursive { get; set; }
|
||||||
[Parameter] public SwitchParameter IncludeImports { get; set; }
|
[Parameter] public SwitchParameter IncludeImports { get; set; }
|
||||||
[Parameter] public SwitchParameter IncludePersonalOverrides { get; set; }
|
[Parameter] public SwitchParameter IncludePersonalOverrides { get; set; }
|
||||||
@@ -34,6 +35,7 @@ namespace PSInfisicalAPI.Cmdlets
|
|||||||
ProjectId = ProjectId,
|
ProjectId = ProjectId,
|
||||||
Environment = Environment,
|
Environment = Environment,
|
||||||
SecretPath = SecretPath,
|
SecretPath = SecretPath,
|
||||||
|
ApiVersion = ApiVersion,
|
||||||
Recursive = Recursive.IsPresent,
|
Recursive = Recursive.IsPresent,
|
||||||
IncludeImports = IncludeImports.IsPresent,
|
IncludeImports = IncludeImports.IsPresent,
|
||||||
IncludePersonalOverrides = IncludePersonalOverrides.IsPresent,
|
IncludePersonalOverrides = IncludePersonalOverrides.IsPresent,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Security;
|
using System.Security;
|
||||||
using PSInfisicalAPI.Models;
|
using PSInfisicalAPI.Models;
|
||||||
|
|
||||||
@@ -8,6 +9,7 @@ namespace PSInfisicalAPI.Connections
|
|||||||
{
|
{
|
||||||
public Uri BaseUri { get; set; }
|
public Uri BaseUri { get; set; }
|
||||||
public string ApiVersion { get; set; }
|
public string ApiVersion { get; set; }
|
||||||
|
public string PinnedApiVersion { get; set; }
|
||||||
public InfisicalAuthType AuthType { get; set; }
|
public InfisicalAuthType AuthType { get; set; }
|
||||||
public string OrganizationId { get; set; }
|
public string OrganizationId { get; set; }
|
||||||
public string ProjectId { get; set; }
|
public string ProjectId { get; set; }
|
||||||
@@ -17,6 +19,8 @@ namespace PSInfisicalAPI.Connections
|
|||||||
public DateTimeOffset? ExpiresAtUtc { get; set; }
|
public DateTimeOffset? ExpiresAtUtc { get; set; }
|
||||||
public bool IsConnected { get; set; }
|
public bool IsConnected { get; set; }
|
||||||
|
|
||||||
|
public Dictionary<string, string> ResolvedEndpointVersions { get; } = new Dictionary<string, string>(StringComparer.Ordinal);
|
||||||
|
|
||||||
internal SecureString AccessToken { get; set; }
|
internal SecureString AccessToken { get; set; }
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
|
|||||||
@@ -5,11 +5,13 @@ namespace PSInfisicalAPI.Endpoints
|
|||||||
{
|
{
|
||||||
public static class InfisicalEndpointRegistry
|
public static class InfisicalEndpointRegistry
|
||||||
{
|
{
|
||||||
private static readonly Dictionary<string, InfisicalEndpointDefinition> Definitions =
|
private static readonly Dictionary<string, List<InfisicalEndpointDefinition>> Candidates =
|
||||||
new Dictionary<string, InfisicalEndpointDefinition>
|
new Dictionary<string, List<InfisicalEndpointDefinition>>
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
InfisicalEndpointNames.UniversalAuthLogin,
|
InfisicalEndpointNames.UniversalAuthLogin,
|
||||||
|
new List<InfisicalEndpointDefinition>
|
||||||
|
{
|
||||||
new InfisicalEndpointDefinition
|
new InfisicalEndpointDefinition
|
||||||
{
|
{
|
||||||
Name = InfisicalEndpointNames.UniversalAuthLogin,
|
Name = InfisicalEndpointNames.UniversalAuthLogin,
|
||||||
@@ -21,9 +23,12 @@ namespace PSInfisicalAPI.Endpoints
|
|||||||
ContainsSecretMaterialInRequest = true,
|
ContainsSecretMaterialInRequest = true,
|
||||||
ContainsSecretMaterialInResponse = true
|
ContainsSecretMaterialInResponse = true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
InfisicalEndpointNames.ListSecrets,
|
InfisicalEndpointNames.ListSecrets,
|
||||||
|
new List<InfisicalEndpointDefinition>
|
||||||
|
{
|
||||||
new InfisicalEndpointDefinition
|
new InfisicalEndpointDefinition
|
||||||
{
|
{
|
||||||
Name = InfisicalEndpointNames.ListSecrets,
|
Name = InfisicalEndpointNames.ListSecrets,
|
||||||
@@ -34,10 +39,24 @@ namespace PSInfisicalAPI.Endpoints
|
|||||||
RequiresAuthorization = true,
|
RequiresAuthorization = true,
|
||||||
ContainsSecretMaterialInRequest = false,
|
ContainsSecretMaterialInRequest = false,
|
||||||
ContainsSecretMaterialInResponse = true
|
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,
|
InfisicalEndpointNames.RetrieveSecret,
|
||||||
|
new List<InfisicalEndpointDefinition>
|
||||||
|
{
|
||||||
new InfisicalEndpointDefinition
|
new InfisicalEndpointDefinition
|
||||||
{
|
{
|
||||||
Name = InfisicalEndpointNames.RetrieveSecret,
|
Name = InfisicalEndpointNames.RetrieveSecret,
|
||||||
@@ -48,24 +67,26 @@ namespace PSInfisicalAPI.Endpoints
|
|||||||
RequiresAuthorization = true,
|
RequiresAuthorization = true,
|
||||||
ContainsSecretMaterialInRequest = false,
|
ContainsSecretMaterialInRequest = false,
|
||||||
ContainsSecretMaterialInResponse = true
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public static InfisicalEndpointDefinition Get(string name)
|
public static InfisicalEndpointDefinition Get(string name)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(name))
|
List<InfisicalEndpointDefinition> list = GetCandidatesInternal(name);
|
||||||
{
|
return list[0];
|
||||||
throw new InfisicalConfigurationException("Endpoint name must be provided.");
|
|
||||||
}
|
|
||||||
|
|
||||||
InfisicalEndpointDefinition definition;
|
|
||||||
if (!Definitions.TryGetValue(name, out definition))
|
|
||||||
{
|
|
||||||
throw new InfisicalConfigurationException(string.Concat("Unknown endpoint name: ", name));
|
|
||||||
}
|
|
||||||
|
|
||||||
return definition;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool TryGet(string name, out InfisicalEndpointDefinition definition)
|
public static bool TryGet(string name, out InfisicalEndpointDefinition definition)
|
||||||
@@ -76,12 +97,50 @@ namespace PSInfisicalAPI.Endpoints
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Definitions.TryGetValue(name, out definition);
|
List<InfisicalEndpointDefinition> list;
|
||||||
|
if (!Candidates.TryGetValue(name, out list) || list == null || list.Count == 0)
|
||||||
|
{
|
||||||
|
definition = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
definition = list[0];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IReadOnlyList<InfisicalEndpointDefinition> GetCandidates(string name)
|
||||||
|
{
|
||||||
|
return GetCandidatesInternal(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IEnumerable<InfisicalEndpointDefinition> All()
|
public static IEnumerable<InfisicalEndpointDefinition> All()
|
||||||
{
|
{
|
||||||
return Definitions.Values;
|
List<InfisicalEndpointDefinition> result = new List<InfisicalEndpointDefinition>();
|
||||||
|
foreach (List<InfisicalEndpointDefinition> list in Candidates.Values)
|
||||||
|
{
|
||||||
|
foreach (InfisicalEndpointDefinition definition in list)
|
||||||
|
{
|
||||||
|
result.Add(definition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<InfisicalEndpointDefinition> GetCandidatesInternal(string name)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(name))
|
||||||
|
{
|
||||||
|
throw new InfisicalConfigurationException("Endpoint name must be provided.");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<InfisicalEndpointDefinition> list;
|
||||||
|
if (!Candidates.TryGetValue(name, out list) || list == null || list.Count == 0)
|
||||||
|
{
|
||||||
|
throw new InfisicalConfigurationException(string.Concat("Unknown endpoint name: ", name));
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,13 +39,7 @@ namespace PSInfisicalAPI.Logging
|
|||||||
public void Error(string component, string message)
|
public void Error(string component, string message)
|
||||||
{
|
{
|
||||||
string line = InfisicalLogFormatter.FormatNow(InfisicalLogLevel.Error, component, message);
|
string line = InfisicalLogFormatter.FormatNow(InfisicalLogLevel.Error, component, message);
|
||||||
ErrorRecord record = new ErrorRecord(
|
_cmdlet.WriteWarning(line);
|
||||||
new InvalidOperationException(message ?? string.Empty),
|
|
||||||
"PSInfisicalAPI.Error",
|
|
||||||
ErrorCategory.NotSpecified,
|
|
||||||
component);
|
|
||||||
record.ErrorDetails = new ErrorDetails(line);
|
|
||||||
_cmdlet.WriteError(record);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ namespace PSInfisicalAPI.Secrets
|
|||||||
public string ProjectId { get; set; }
|
public string ProjectId { get; set; }
|
||||||
public string Environment { get; set; }
|
public string Environment { get; set; }
|
||||||
public string SecretPath { get; set; }
|
public string SecretPath { get; set; }
|
||||||
|
public string ApiVersion { get; set; }
|
||||||
public bool Recursive { get; set; }
|
public bool Recursive { get; set; }
|
||||||
public bool? IncludeImports { get; set; }
|
public bool? IncludeImports { get; set; }
|
||||||
public bool IncludePersonalOverrides { get; set; }
|
public bool IncludePersonalOverrides { get; set; }
|
||||||
@@ -22,6 +23,7 @@ namespace PSInfisicalAPI.Secrets
|
|||||||
public string ProjectId { get; set; }
|
public string ProjectId { get; set; }
|
||||||
public string Environment { get; set; }
|
public string Environment { get; set; }
|
||||||
public string SecretPath { get; set; }
|
public string SecretPath { get; set; }
|
||||||
|
public string ApiVersion { get; set; }
|
||||||
public int? Version { get; set; }
|
public int? Version { get; set; }
|
||||||
public string Type { get; set; }
|
public string Type { get; set; }
|
||||||
public bool? ViewSecretValue { get; set; }
|
public bool? ViewSecretValue { get; set; }
|
||||||
|
|||||||
@@ -32,10 +32,11 @@ namespace PSInfisicalAPI.Secrets
|
|||||||
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
|
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
|
||||||
if (query == null) { throw new ArgumentNullException(nameof(query)); }
|
if (query == null) { throw new ArgumentNullException(nameof(query)); }
|
||||||
|
|
||||||
InfisicalEndpointDefinition definition = InfisicalEndpointRegistry.Get(InfisicalEndpointNames.ListSecrets);
|
string resolvedProjectId = FirstNonEmpty(query.ProjectId, connection.ProjectId);
|
||||||
|
|
||||||
List<KeyValuePair<string, string>> queryParameters = new List<KeyValuePair<string, string>>();
|
List<KeyValuePair<string, string>> queryParameters = new List<KeyValuePair<string, string>>();
|
||||||
AddIfNotNull(queryParameters, "projectId", FirstNonEmpty(query.ProjectId, connection.ProjectId));
|
AddIfNotNull(queryParameters, "workspaceId", resolvedProjectId);
|
||||||
|
AddIfNotNull(queryParameters, "projectId", resolvedProjectId);
|
||||||
AddIfNotNull(queryParameters, "environment", FirstNonEmpty(query.Environment, connection.Environment));
|
AddIfNotNull(queryParameters, "environment", FirstNonEmpty(query.Environment, connection.Environment));
|
||||||
AddIfNotNull(queryParameters, "secretPath", FirstNonEmpty(query.SecretPath, connection.DefaultSecretPath, "/"));
|
AddIfNotNull(queryParameters, "secretPath", FirstNonEmpty(query.SecretPath, connection.DefaultSecretPath, "/"));
|
||||||
queryParameters.Add(new KeyValuePair<string, string>("recursive", query.Recursive ? "true" : "false"));
|
queryParameters.Add(new KeyValuePair<string, string>("recursive", query.Recursive ? "true" : "false"));
|
||||||
@@ -60,13 +61,18 @@ namespace PSInfisicalAPI.Secrets
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Uri uri = InfisicalUriBuilder.Build(connection.BaseUri, definition, null, queryParameters);
|
|
||||||
InfisicalHttpResponse response = ExecuteAuthorized(connection, definition, "RetrieveSecrets", uri, null);
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.Information(Component, "Attempting to retrieve Infisical secrets. Please Wait...");
|
_logger.Information(Component, "Attempting to retrieve Infisical secrets. Please Wait...");
|
||||||
EnsureSuccess(response, definition);
|
|
||||||
|
InfisicalHttpResponse response = SendWithVersionFallback(
|
||||||
|
connection,
|
||||||
|
InfisicalEndpointNames.ListSecrets,
|
||||||
|
query.ApiVersion,
|
||||||
|
"RetrieveSecrets",
|
||||||
|
null,
|
||||||
|
queryParameters,
|
||||||
|
null);
|
||||||
|
|
||||||
InfisicalSecretListResponseDto dto = _serializer.Deserialize<InfisicalSecretListResponseDto>(response.Body);
|
InfisicalSecretListResponseDto dto = _serializer.Deserialize<InfisicalSecretListResponseDto>(response.Body);
|
||||||
response.Clear();
|
response.Clear();
|
||||||
@@ -88,12 +94,13 @@ namespace PSInfisicalAPI.Secrets
|
|||||||
if (query == null) { throw new ArgumentNullException(nameof(query)); }
|
if (query == null) { throw new ArgumentNullException(nameof(query)); }
|
||||||
if (string.IsNullOrEmpty(query.SecretName)) { throw new InfisicalConfigurationException("SecretName is required."); }
|
if (string.IsNullOrEmpty(query.SecretName)) { throw new InfisicalConfigurationException("SecretName is required."); }
|
||||||
|
|
||||||
InfisicalEndpointDefinition definition = InfisicalEndpointRegistry.Get(InfisicalEndpointNames.RetrieveSecret);
|
|
||||||
|
|
||||||
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "secretName", query.SecretName } };
|
Dictionary<string, string> pathParameters = new Dictionary<string, string> { { "secretName", query.SecretName } };
|
||||||
|
|
||||||
|
string resolvedProjectId = FirstNonEmpty(query.ProjectId, connection.ProjectId);
|
||||||
|
|
||||||
List<KeyValuePair<string, string>> queryParameters = new List<KeyValuePair<string, string>>();
|
List<KeyValuePair<string, string>> queryParameters = new List<KeyValuePair<string, string>>();
|
||||||
AddIfNotNull(queryParameters, "projectId", FirstNonEmpty(query.ProjectId, connection.ProjectId));
|
AddIfNotNull(queryParameters, "workspaceId", resolvedProjectId);
|
||||||
|
AddIfNotNull(queryParameters, "projectId", resolvedProjectId);
|
||||||
AddIfNotNull(queryParameters, "environment", FirstNonEmpty(query.Environment, connection.Environment));
|
AddIfNotNull(queryParameters, "environment", FirstNonEmpty(query.Environment, connection.Environment));
|
||||||
AddIfNotNull(queryParameters, "secretPath", FirstNonEmpty(query.SecretPath, connection.DefaultSecretPath, "/"));
|
AddIfNotNull(queryParameters, "secretPath", FirstNonEmpty(query.SecretPath, connection.DefaultSecretPath, "/"));
|
||||||
AddIfNotNull(queryParameters, "type", string.IsNullOrEmpty(query.Type) ? "shared" : query.Type.ToLowerInvariant());
|
AddIfNotNull(queryParameters, "type", string.IsNullOrEmpty(query.Type) ? "shared" : query.Type.ToLowerInvariant());
|
||||||
@@ -102,13 +109,18 @@ namespace PSInfisicalAPI.Secrets
|
|||||||
if (query.ExpandSecretReferences.HasValue) { queryParameters.Add(new KeyValuePair<string, string>("expandSecretReferences", query.ExpandSecretReferences.Value ? "true" : "false")); }
|
if (query.ExpandSecretReferences.HasValue) { queryParameters.Add(new KeyValuePair<string, string>("expandSecretReferences", query.ExpandSecretReferences.Value ? "true" : "false")); }
|
||||||
if (query.IncludeImports.HasValue) { queryParameters.Add(new KeyValuePair<string, string>("includeImports", query.IncludeImports.Value ? "true" : "false")); }
|
if (query.IncludeImports.HasValue) { queryParameters.Add(new KeyValuePair<string, string>("includeImports", query.IncludeImports.Value ? "true" : "false")); }
|
||||||
|
|
||||||
Uri uri = InfisicalUriBuilder.Build(connection.BaseUri, definition, pathParameters, queryParameters);
|
|
||||||
InfisicalHttpResponse response = ExecuteAuthorized(connection, definition, "RetrieveSecret", uri, null);
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.Information(Component, string.Concat("Attempting to retrieve Infisical secret '", query.SecretName, "'. Please Wait..."));
|
_logger.Information(Component, string.Concat("Attempting to retrieve Infisical secret '", query.SecretName, "'. Please Wait..."));
|
||||||
EnsureSuccess(response, definition);
|
|
||||||
|
InfisicalHttpResponse response = SendWithVersionFallback(
|
||||||
|
connection,
|
||||||
|
InfisicalEndpointNames.RetrieveSecret,
|
||||||
|
query.ApiVersion,
|
||||||
|
"RetrieveSecret",
|
||||||
|
pathParameters,
|
||||||
|
queryParameters,
|
||||||
|
null);
|
||||||
|
|
||||||
InfisicalSecretSingleResponseDto dto = _serializer.Deserialize<InfisicalSecretSingleResponseDto>(response.Body);
|
InfisicalSecretSingleResponseDto dto = _serializer.Deserialize<InfisicalSecretSingleResponseDto>(response.Body);
|
||||||
response.Clear();
|
response.Clear();
|
||||||
@@ -124,6 +136,160 @@ namespace PSInfisicalAPI.Secrets
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private InfisicalHttpResponse SendWithVersionFallback(
|
||||||
|
InfisicalConnection connection,
|
||||||
|
string endpointName,
|
||||||
|
string perCallApiVersion,
|
||||||
|
string operationName,
|
||||||
|
Dictionary<string, string> pathParameters,
|
||||||
|
List<KeyValuePair<string, string>> queryParameters,
|
||||||
|
string body)
|
||||||
|
{
|
||||||
|
IReadOnlyList<InfisicalEndpointDefinition> allCandidates = InfisicalEndpointRegistry.GetCandidates(endpointName);
|
||||||
|
|
||||||
|
string pinned = FirstNonEmpty(perCallApiVersion, connection.PinnedApiVersion);
|
||||||
|
string cached;
|
||||||
|
connection.ResolvedEndpointVersions.TryGetValue(endpointName, out cached);
|
||||||
|
|
||||||
|
List<InfisicalEndpointDefinition> candidates = OrderCandidates(allCandidates, pinned, cached);
|
||||||
|
|
||||||
|
if (candidates.Count == 0)
|
||||||
|
{
|
||||||
|
throw new InfisicalConfigurationException(string.Concat(
|
||||||
|
"No matching endpoint candidate for '", endpointName,
|
||||||
|
"' with ApiVersion='", pinned ?? string.Empty, "'."));
|
||||||
|
}
|
||||||
|
|
||||||
|
InfisicalApiException lastException = null;
|
||||||
|
|
||||||
|
for (int index = 0; index < candidates.Count; index++)
|
||||||
|
{
|
||||||
|
InfisicalEndpointDefinition definition = candidates[index];
|
||||||
|
Uri uri = InfisicalUriBuilder.Build(connection.BaseUri, definition, pathParameters, queryParameters);
|
||||||
|
InfisicalHttpResponse response = ExecuteAuthorized(connection, definition, operationName, uri, body);
|
||||||
|
|
||||||
|
if (response.StatusCode >= 200 && response.StatusCode < 300)
|
||||||
|
{
|
||||||
|
connection.ResolvedEndpointVersions[endpointName] = definition.Version;
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
InfisicalApiException exception = BuildApiException(response, definition);
|
||||||
|
response.Clear();
|
||||||
|
|
||||||
|
bool hasMoreCandidates = (index + 1) < candidates.Count;
|
||||||
|
bool pinnedHere = !string.IsNullOrEmpty(pinned);
|
||||||
|
|
||||||
|
if (!pinnedHere && hasMoreCandidates && IsVersionMismatch(exception))
|
||||||
|
{
|
||||||
|
_logger.Warning(Component, string.Concat(
|
||||||
|
"Endpoint '", endpointName, "' version '", definition.Version,
|
||||||
|
"' rejected by server (", exception.StatusCode.ToString(CultureInfo.InvariantCulture),
|
||||||
|
"); falling back to next candidate."));
|
||||||
|
lastException = exception;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw lastException ?? new InfisicalApiException(string.Concat(
|
||||||
|
"All API version candidates exhausted for '", endpointName, "'."));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<InfisicalEndpointDefinition> OrderCandidates(
|
||||||
|
IReadOnlyList<InfisicalEndpointDefinition> allCandidates,
|
||||||
|
string pinned,
|
||||||
|
string cached)
|
||||||
|
{
|
||||||
|
List<InfisicalEndpointDefinition> ordered = new List<InfisicalEndpointDefinition>();
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(pinned))
|
||||||
|
{
|
||||||
|
foreach (InfisicalEndpointDefinition candidate in allCandidates)
|
||||||
|
{
|
||||||
|
if (string.Equals(candidate.Version, pinned, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
ordered.Add(candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ordered;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(cached))
|
||||||
|
{
|
||||||
|
foreach (InfisicalEndpointDefinition candidate in allCandidates)
|
||||||
|
{
|
||||||
|
if (string.Equals(candidate.Version, cached, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
ordered.Add(candidate);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (InfisicalEndpointDefinition candidate in allCandidates)
|
||||||
|
{
|
||||||
|
if (!string.Equals(candidate.Version, cached, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
ordered.Add(candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ordered;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (InfisicalEndpointDefinition candidate in allCandidates)
|
||||||
|
{
|
||||||
|
ordered.Add(candidate);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ordered;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsVersionMismatch(InfisicalApiException exception)
|
||||||
|
{
|
||||||
|
string body = exception.SanitizedBody;
|
||||||
|
bool hasInfisicalErrorEnvelope = !string.IsNullOrEmpty(body)
|
||||||
|
&& body.IndexOf("\"reqId\"", StringComparison.OrdinalIgnoreCase) >= 0
|
||||||
|
&& body.IndexOf("\"error\"", StringComparison.OrdinalIgnoreCase) >= 0;
|
||||||
|
|
||||||
|
if (exception.StatusCode == 405)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exception.StatusCode == 404 && !hasInfisicalErrorEnvelope)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exception.StatusCode == 400 && !string.IsNullOrEmpty(body))
|
||||||
|
{
|
||||||
|
if (body.IndexOf("projectSlug", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||||
|
body.IndexOf("workspaceId", StringComparison.OrdinalIgnoreCase) >= 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
private InfisicalHttpResponse ExecuteAuthorized(InfisicalConnection connection, InfisicalEndpointDefinition definition, string operationName, Uri uri, string body)
|
private InfisicalHttpResponse ExecuteAuthorized(InfisicalConnection connection, InfisicalEndpointDefinition definition, string operationName, Uri uri, string body)
|
||||||
{
|
{
|
||||||
Dictionary<string, string> headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
Dictionary<string, string> headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
@@ -157,23 +323,6 @@ namespace PSInfisicalAPI.Secrets
|
|||||||
return _httpClient.Send(request);
|
return _httpClient.Send(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void EnsureSuccess(InfisicalHttpResponse response, InfisicalEndpointDefinition definition)
|
|
||||||
{
|
|
||||||
if (response.StatusCode >= 200 && response.StatusCode < 300)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = definition.ContainsSecretMaterialInResponse ? "[REDACTED]" : response.Body;
|
|
||||||
response.Clear();
|
|
||||||
throw exception;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void AddIfNotNull(List<KeyValuePair<string, string>> list, string key, string value)
|
private static void AddIfNotNull(List<KeyValuePair<string, string>> list, string key, string value)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(value))
|
if (!string.IsNullOrEmpty(value))
|
||||||
|
|||||||
Reference in New Issue
Block a user