Treat a missing cert-manager project as an empty result rather than an error
An organization that has not set up Certificate Manager has nothing to list, so resolution now returns nothing instead of throwing, and the PKI Get-* cmdlets return quietly. -Verbose explains what happened and what to do: -ProjectId was not supplied and this organization has no Certificate Manager project, so there is nothing to resolve to. Create one in Infisical (Certificate Management), or pass -ProjectId to target a specific project. Without the guard the null would have travelled to the client layer and surfaced as "ProjectId is required" from somewhere unrelated to the cause, so each Get-* cmdlet returns as soon as resolution comes back empty. Request-InfisicalCertificate deliberately proceeds, because profile issuance does not need a project: only the reuse search does, and that already degrades to a local match with a warning when the search cannot run. Resolution is now attempted once per cmdlet instance rather than once per non-null result, so an organization with no project does not re-query on every call. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+8
-1
@@ -6,11 +6,17 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) loos
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 2026.07.31.2006
|
||||
|
||||
- Build produced from commit 276958e3a83a.
|
||||
|
||||
## Unreleased (carried forward)
|
||||
|
||||
## 2026.07.31.1958
|
||||
|
||||
- Build produced from commit 633f40c1fa54.
|
||||
|
||||
## Unreleased (carried forward)
|
||||
## Unreleased (carried forward)
|
||||
|
||||
## 2026.07.31.1924
|
||||
|
||||
@@ -50,6 +56,7 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) loos
|
||||
|
||||
### Changed (project scoping, follow-up)
|
||||
|
||||
- An organization with **no** Certificate Manager project is no longer an error. Resolution returns nothing and the PKI `Get-*` cmdlets emit no output, with `-Verbose` explaining that none was found and suggesting one be created. Nothing to list is an empty result, not a failure.
|
||||
- Several Certificate Manager projects in one organization is **no longer an error**. Infisical designates one as the organization's active project and serves certificate applications only from it, so resolution now picks that one; when none is designated the first is used and the verbose line says so.
|
||||
- `InfisicalOrganization.DefaultCertManagerProjectId` exposes the organization's active Certificate Manager project.
|
||||
- `Get-InfisicalProject` now calls `/api/v1/projects`, keeping the previously used `/api/v1/workspace` as a fallback candidate — that route mounts Infisical's deprecated project router.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@{
|
||||
RootModule = 'PSInfisicalAPI.psm1'
|
||||
ModuleVersion = '2026.07.31.1958'
|
||||
ModuleVersion = '2026.07.31.2006'
|
||||
GUID = 'b8a2f3d4-7c51-4d2f-9e6a-1f0c8b3d4e51'
|
||||
Author = 'Grace Solutions'
|
||||
CompanyName = 'Grace Solutions'
|
||||
@@ -74,7 +74,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 = '633f40c1fa54'
|
||||
CommitHash = '276958e3a83a'
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -216,6 +216,14 @@ VERBOSE: -ProjectId was not supplied; resolved the organization's only Certifica
|
||||
'Microsoft Endpoint Configuration Manager' (2122628e-...).
|
||||
```
|
||||
|
||||
An organization with **no** Certificate Manager project is not an error either. There is nothing to list, so the PKI `Get-*` cmdlets return nothing and `-Verbose` explains why:
|
||||
|
||||
```text
|
||||
VERBOSE: -ProjectId was not supplied and this organization has no Certificate Manager project, so there is
|
||||
nothing to resolve to. Create one in Infisical (Certificate Management), or pass -ProjectId to
|
||||
target a specific project.
|
||||
```
|
||||
|
||||
Several Certificate Manager projects in one organization is not an error. Infisical designates one as the organization's **active** project, and that is what resolution picks:
|
||||
|
||||
```text
|
||||
|
||||
@@ -109,6 +109,36 @@ namespace PSInfisicalAPI.Tests
|
||||
Assert.Equal(typeof(PSInfisicalAPI.Models.InfisicalProject), finder.ReturnType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void An_Organization_Without_Any_Cert_Manager_Project_Is_Not_An_Error()
|
||||
{
|
||||
// Nothing to list is an empty result, not a failure, so resolution returns null and each Get-*
|
||||
// cmdlet returns quietly rather than surfacing "ProjectId is required" from deep in the client.
|
||||
MethodInfo resolver = typeof(PSInfisicalAPI.Cmdlets.InfisicalCmdletBase)
|
||||
.GetMethod("ResolveCertManagerProjectId", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
|
||||
List<string> called = GetCalledMethodNames(resolver);
|
||||
Assert.DoesNotContain("ThrowTerminatingForException", called);
|
||||
Assert.DoesNotContain("WriteErrorForException", called);
|
||||
|
||||
List<string> ungarded = new List<string>();
|
||||
foreach (string typeName in PkiCmdletTypes)
|
||||
{
|
||||
// Request-InfisicalCertificate can still issue through a profile without a project, so it is
|
||||
// deliberately allowed to proceed.
|
||||
if (typeName.EndsWith("RequestInfisicalCertificateCmdlet", StringComparison.Ordinal)) { continue; }
|
||||
|
||||
Type type = ModuleAssembly.GetType(typeName, true);
|
||||
MethodInfo processRecord = type.GetMethod("ProcessRecord", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
|
||||
if (!GetCalledMethodNames(processRecord).Contains("IsNullOrEmpty"))
|
||||
{
|
||||
ungarded.Add(type.Name);
|
||||
}
|
||||
}
|
||||
|
||||
Assert.Empty(ungarded);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void The_Organizations_Active_Cert_Manager_Project_Is_Modelled()
|
||||
{
|
||||
|
||||
@@ -34,6 +34,9 @@ namespace PSInfisicalAPI.Cmdlets
|
||||
// one; -ProjectId is optional here for the same reason. Assigned back so every call below
|
||||
// sees the resolved value without threading a second variable through.
|
||||
ProjectId = ResolveCertManagerProjectId(connection, ProjectId);
|
||||
|
||||
// No Certificate Manager project means nothing to list; that is an empty result, not a failure.
|
||||
if (string.IsNullOrEmpty(ProjectId)) { return; }
|
||||
InfisicalPkiClient client = new InfisicalPkiClient(HttpClient, Logger);
|
||||
|
||||
if (string.Equals(ParameterSetName, "ById", StringComparison.Ordinal))
|
||||
|
||||
@@ -30,6 +30,9 @@ namespace PSInfisicalAPI.Cmdlets
|
||||
// one; -ProjectId is optional here for the same reason. Assigned back so every call below
|
||||
// sees the resolved value without threading a second variable through.
|
||||
ProjectId = ResolveCertManagerProjectId(connection, ProjectId);
|
||||
|
||||
// No Certificate Manager project means nothing to list; that is an empty result, not a failure.
|
||||
if (string.IsNullOrEmpty(ProjectId)) { return; }
|
||||
InfisicalPkiClient client = new InfisicalPkiClient(HttpClient, Logger);
|
||||
|
||||
InfisicalCertificateApplicationEnrollment enrollment = client.GetCertificateApplicationEnrollment(connection, ApplicationId, ProfileId, ProjectId);
|
||||
|
||||
@@ -30,6 +30,9 @@ namespace PSInfisicalAPI.Cmdlets
|
||||
// one; -ProjectId is optional here for the same reason. Assigned back so every call below
|
||||
// sees the resolved value without threading a second variable through.
|
||||
ProjectId = ResolveCertManagerProjectId(connection, ProjectId);
|
||||
|
||||
// No Certificate Manager project means nothing to list; that is an empty result, not a failure.
|
||||
if (string.IsNullOrEmpty(ProjectId)) { return; }
|
||||
InfisicalPkiClient client = new InfisicalPkiClient(HttpClient, Logger);
|
||||
|
||||
if (string.Equals(ParameterSetName, "ById", StringComparison.Ordinal))
|
||||
|
||||
@@ -76,6 +76,9 @@ namespace PSInfisicalAPI.Cmdlets
|
||||
// sees the resolved value without threading a second variable through.
|
||||
ProjectId = ResolveCertManagerProjectId(connection, ProjectId);
|
||||
|
||||
// No Certificate Manager project means nothing to list; that is an empty result, not a failure.
|
||||
if (string.IsNullOrEmpty(ProjectId)) { return; }
|
||||
|
||||
InfisicalCertificateSearchQuery query = new InfisicalCertificateSearchQuery
|
||||
{
|
||||
ProjectId = ProjectId,
|
||||
|
||||
@@ -30,6 +30,9 @@ namespace PSInfisicalAPI.Cmdlets
|
||||
// one; -ProjectId is optional here for the same reason. Assigned back so every call below
|
||||
// sees the resolved value without threading a second variable through.
|
||||
ProjectId = ResolveCertManagerProjectId(connection, ProjectId);
|
||||
|
||||
// No Certificate Manager project means nothing to list; that is an empty result, not a failure.
|
||||
if (string.IsNullOrEmpty(ProjectId)) { return; }
|
||||
InfisicalPkiClient client = new InfisicalPkiClient(HttpClient, Logger);
|
||||
|
||||
if (string.Equals(ParameterSetName, "ById", StringComparison.Ordinal))
|
||||
|
||||
@@ -44,6 +44,9 @@ namespace PSInfisicalAPI.Cmdlets
|
||||
// one; -ProjectId is optional here for the same reason. Assigned back so every call below
|
||||
// sees the resolved value without threading a second variable through.
|
||||
ProjectId = ResolveCertManagerProjectId(connection, ProjectId);
|
||||
|
||||
// No Certificate Manager project means nothing to list; that is an empty result, not a failure.
|
||||
if (string.IsNullOrEmpty(ProjectId)) { return; }
|
||||
InfisicalPkiClient client = new InfisicalPkiClient(HttpClient, Logger);
|
||||
|
||||
if (string.Equals(ParameterSetName, "ById", StringComparison.Ordinal))
|
||||
|
||||
@@ -26,6 +26,9 @@ namespace PSInfisicalAPI.Cmdlets
|
||||
// one; -ProjectId is optional here for the same reason. Assigned back so every call below
|
||||
// sees the resolved value without threading a second variable through.
|
||||
ProjectId = ResolveCertManagerProjectId(connection, ProjectId);
|
||||
|
||||
// No Certificate Manager project means nothing to list; that is an empty result, not a failure.
|
||||
if (string.IsNullOrEmpty(ProjectId)) { return; }
|
||||
InfisicalPkiClient client = new InfisicalPkiClient(HttpClient, Logger);
|
||||
|
||||
if (string.Equals(ParameterSetName, "ByName", StringComparison.Ordinal))
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace PSInfisicalAPI.Cmdlets
|
||||
private IInfisicalHttpClient _httpClient;
|
||||
private bool? _isElevated;
|
||||
private string _resolvedCertManagerProjectId;
|
||||
private bool _certManagerProjectResolved;
|
||||
|
||||
protected IInfisicalLogger Logger
|
||||
{
|
||||
@@ -70,10 +71,17 @@ namespace PSInfisicalAPI.Cmdlets
|
||||
/// in the URL path and cannot defer to the server's resolver. Resolved once per cmdlet instance.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The project to use, or <c>null</c> when the organization has no Certificate Manager project. Callers
|
||||
/// that cannot proceed without one should return quietly rather than failing: an organization that has
|
||||
/// not set up Certificate Manager has nothing to list, which is an empty result and not an error.
|
||||
/// </returns>
|
||||
protected string ResolveCertManagerProjectId(InfisicalConnection connection, string explicitValue)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(explicitValue)) { return explicitValue; }
|
||||
if (_resolvedCertManagerProjectId != null) { return _resolvedCertManagerProjectId; }
|
||||
if (_certManagerProjectResolved) { return _resolvedCertManagerProjectId; }
|
||||
|
||||
_certManagerProjectResolved = true;
|
||||
|
||||
InfisicalProjectClient client = new InfisicalProjectClient(HttpClient, Logger);
|
||||
InfisicalProject[] projects = client.List(connection, CertManagerProjectType, false);
|
||||
@@ -93,8 +101,13 @@ namespace PSInfisicalAPI.Cmdlets
|
||||
|
||||
if (certManagerProjects.Count == 0)
|
||||
{
|
||||
throw new InfisicalConfigurationException(
|
||||
"This organization has no Certificate Manager project, so there is nothing to resolve -ProjectId to. Create one in Infisical, or pass -ProjectId explicitly.");
|
||||
Logger.Verbose(GetType().Name, string.Concat(
|
||||
"-ProjectId was not supplied and this organization has no Certificate Manager project, so there is ",
|
||||
"nothing to resolve to. Create one in Infisical (Certificate Management), or pass -ProjectId to ",
|
||||
"target a specific project."));
|
||||
|
||||
_resolvedCertManagerProjectId = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
InfisicalProject chosen = certManagerProjects[0];
|
||||
|
||||
Reference in New Issue
Block a user