Add lazy API version negotiation (v4 -> v3 fallback) with -ApiVersion override
- Endpoint registry now stores ordered candidate lists per logical operation; Get/TryGet preserve prior behavior, new GetCandidates(name) exposes the ladder. Added v3 fallbacks (/api/v3/secrets/raw and /api/v3/secrets/raw/{secretName}) after v4. - InfisicalConnection gains PinnedApiVersion and a ResolvedEndpointVersions cache so the chosen version sticks for the session. - InfisicalSecretsClient.SendWithVersionFallback walks candidates in pin -> cached -> registry order, falls back on routing-style failures (404 without an Infisical JSON envelope, 405, or 400 mentioning workspaceId/projectSlug) when no version is pinned, and surfaces real application errors immediately. - Get-InfisicalSecret(s) expose -ApiVersion; Connect-Infisical sets PinnedApiVersion only when -ApiVersion is explicitly bound on the command line (env-var/default values do not pin). - Logger.Error routes via WriteWarning to avoid premature terminating errors that masked InfisicalApiException details; EnsureSuccess no longer redacts non-2xx bodies so server error envelopes are visible. - InfisicalSecretsClient sends both projectId and workspaceId so it works against both new and legacy server-side validators.
This commit is contained in:
@@ -6,6 +6,36 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) loos
|
||||
|
||||
## 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
|
||||
|
||||
- Build produced from commit c86676010532.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@{
|
||||
RootModule = 'PSInfisicalAPI.psm1'
|
||||
ModuleVersion = '2026.06.03.0032'
|
||||
ModuleVersion = '2026.06.03.0057'
|
||||
GUID = 'b8a2f3d4-7c51-4d2f-9e6a-1f0c8b3d4e51'
|
||||
Author = 'Grace Solutions'
|
||||
CompanyName = 'Grace Solutions'
|
||||
@@ -27,7 +27,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 = 'c86676010532'
|
||||
CommitHash = '7e5209190ac2'
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -89,10 +89,13 @@ namespace PSInfisicalAPI.Cmdlets
|
||||
throw new InfisicalAuthenticationException("Authentication did not produce an access token.");
|
||||
}
|
||||
|
||||
bool apiVersionExplicitlyBound = MyInvocation.BoundParameters.ContainsKey("ApiVersion");
|
||||
|
||||
InfisicalConnection connection = new InfisicalConnection
|
||||
{
|
||||
BaseUri = BaseUri,
|
||||
ApiVersion = ApiVersion,
|
||||
PinnedApiVersion = apiVersionExplicitlyBound ? ApiVersion : null,
|
||||
AuthType = authType,
|
||||
OrganizationId = OrganizationId,
|
||||
ProjectId = ProjectId,
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace PSInfisicalAPI.Cmdlets
|
||||
[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 int? Version { get; set; }
|
||||
[Parameter] public InfisicalSecretType Type { get; set; } = InfisicalSecretType.Shared;
|
||||
[Parameter] public SwitchParameter ViewSecretValue { get; set; }
|
||||
@@ -34,6 +35,7 @@ namespace PSInfisicalAPI.Cmdlets
|
||||
ProjectId = ProjectId,
|
||||
Environment = Environment,
|
||||
SecretPath = SecretPath,
|
||||
ApiVersion = ApiVersion,
|
||||
Version = Version,
|
||||
Type = Type.ToString(),
|
||||
ViewSecretValue = ViewSecretValue.IsPresent,
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace PSInfisicalAPI.Cmdlets
|
||||
[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 SwitchParameter Recursive { get; set; }
|
||||
[Parameter] public SwitchParameter IncludeImports { get; set; }
|
||||
[Parameter] public SwitchParameter IncludePersonalOverrides { get; set; }
|
||||
@@ -34,6 +35,7 @@ namespace PSInfisicalAPI.Cmdlets
|
||||
ProjectId = ProjectId,
|
||||
Environment = Environment,
|
||||
SecretPath = SecretPath,
|
||||
ApiVersion = ApiVersion,
|
||||
Recursive = Recursive.IsPresent,
|
||||
IncludeImports = IncludeImports.IsPresent,
|
||||
IncludePersonalOverrides = IncludePersonalOverrides.IsPresent,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security;
|
||||
using PSInfisicalAPI.Models;
|
||||
|
||||
@@ -8,6 +9,7 @@ namespace PSInfisicalAPI.Connections
|
||||
{
|
||||
public Uri BaseUri { get; set; }
|
||||
public string ApiVersion { get; set; }
|
||||
public string PinnedApiVersion { get; set; }
|
||||
public InfisicalAuthType AuthType { get; set; }
|
||||
public string OrganizationId { get; set; }
|
||||
public string ProjectId { get; set; }
|
||||
@@ -17,6 +19,8 @@ namespace PSInfisicalAPI.Connections
|
||||
public DateTimeOffset? ExpiresAtUtc { get; set; }
|
||||
public bool IsConnected { get; set; }
|
||||
|
||||
public Dictionary<string, string> ResolvedEndpointVersions { get; } = new Dictionary<string, string>(StringComparer.Ordinal);
|
||||
|
||||
internal SecureString AccessToken { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
|
||||
@@ -5,11 +5,13 @@ namespace PSInfisicalAPI.Endpoints
|
||||
{
|
||||
public static class InfisicalEndpointRegistry
|
||||
{
|
||||
private static readonly Dictionary<string, InfisicalEndpointDefinition> Definitions =
|
||||
new Dictionary<string, InfisicalEndpointDefinition>
|
||||
private static readonly Dictionary<string, List<InfisicalEndpointDefinition>> Candidates =
|
||||
new Dictionary<string, List<InfisicalEndpointDefinition>>
|
||||
{
|
||||
{
|
||||
InfisicalEndpointNames.UniversalAuthLogin,
|
||||
new List<InfisicalEndpointDefinition>
|
||||
{
|
||||
new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.UniversalAuthLogin,
|
||||
@@ -21,9 +23,12 @@ namespace PSInfisicalAPI.Endpoints
|
||||
ContainsSecretMaterialInRequest = true,
|
||||
ContainsSecretMaterialInResponse = true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
InfisicalEndpointNames.ListSecrets,
|
||||
new List<InfisicalEndpointDefinition>
|
||||
{
|
||||
new InfisicalEndpointDefinition
|
||||
{
|
||||
Name = InfisicalEndpointNames.ListSecrets,
|
||||
@@ -34,10 +39,24 @@ namespace PSInfisicalAPI.Endpoints
|
||||
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,
|
||||
@@ -48,24 +67,26 @@ namespace PSInfisicalAPI.Endpoints
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public static InfisicalEndpointDefinition Get(string name)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
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;
|
||||
List<InfisicalEndpointDefinition> list = GetCandidatesInternal(name);
|
||||
return list[0];
|
||||
}
|
||||
|
||||
public static bool TryGet(string name, out InfisicalEndpointDefinition definition)
|
||||
@@ -76,12 +97,50 @@ namespace PSInfisicalAPI.Endpoints
|
||||
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()
|
||||
{
|
||||
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)
|
||||
{
|
||||
string line = InfisicalLogFormatter.FormatNow(InfisicalLogLevel.Error, component, message);
|
||||
ErrorRecord record = new ErrorRecord(
|
||||
new InvalidOperationException(message ?? string.Empty),
|
||||
"PSInfisicalAPI.Error",
|
||||
ErrorCategory.NotSpecified,
|
||||
component);
|
||||
record.ErrorDetails = new ErrorDetails(line);
|
||||
_cmdlet.WriteError(record);
|
||||
_cmdlet.WriteWarning(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace PSInfisicalAPI.Secrets
|
||||
public string ProjectId { get; set; }
|
||||
public string Environment { get; set; }
|
||||
public string SecretPath { get; set; }
|
||||
public string ApiVersion { get; set; }
|
||||
public bool Recursive { get; set; }
|
||||
public bool? IncludeImports { get; set; }
|
||||
public bool IncludePersonalOverrides { get; set; }
|
||||
@@ -22,6 +23,7 @@ namespace PSInfisicalAPI.Secrets
|
||||
public string ProjectId { get; set; }
|
||||
public string Environment { get; set; }
|
||||
public string SecretPath { get; set; }
|
||||
public string ApiVersion { get; set; }
|
||||
public int? Version { get; set; }
|
||||
public string Type { get; set; }
|
||||
public bool? ViewSecretValue { get; set; }
|
||||
|
||||
@@ -32,10 +32,11 @@ namespace PSInfisicalAPI.Secrets
|
||||
if (connection == null) { throw new ArgumentNullException(nameof(connection)); }
|
||||
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>>();
|
||||
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, "secretPath", FirstNonEmpty(query.SecretPath, connection.DefaultSecretPath, "/"));
|
||||
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
|
||||
{
|
||||
_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);
|
||||
response.Clear();
|
||||
@@ -88,12 +94,13 @@ namespace PSInfisicalAPI.Secrets
|
||||
if (query == null) { throw new ArgumentNullException(nameof(query)); }
|
||||
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 } };
|
||||
|
||||
string resolvedProjectId = FirstNonEmpty(query.ProjectId, connection.ProjectId);
|
||||
|
||||
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, "secretPath", FirstNonEmpty(query.SecretPath, connection.DefaultSecretPath, "/"));
|
||||
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.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
|
||||
{
|
||||
_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);
|
||||
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)
|
||||
{
|
||||
Dictionary<string, string> headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
@@ -157,23 +323,6 @@ namespace PSInfisicalAPI.Secrets
|
||||
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)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(value))
|
||||
|
||||
Reference in New Issue
Block a user