diff --git a/apps/console/src/pages/organizations/compliance-page/domain/_components/CompliancePageDomainCard.tsx b/apps/console/src/pages/organizations/compliance-page/domain/_components/CompliancePageDomainCard.tsx index c7f344555..e2c38623f 100644 --- a/apps/console/src/pages/organizations/compliance-page/domain/_components/CompliancePageDomainCard.tsx +++ b/apps/console/src/pages/organizations/compliance-page/domain/_components/CompliancePageDomainCard.tsx @@ -30,6 +30,7 @@ const fragment = graphql` fragment CompliancePageDomainCardFragment on CustomDomain { domain sslStatus + provisioningError canDelete: permission(action: "core:custom-domain:delete") ...CompliancePageDomainDialogFragment } @@ -52,7 +53,9 @@ export function CompliancePageDomainCard(props: { fKey: CompliancePageDomainCard
{domain.sslStatus === "ACTIVE" ? __("Verified") - : __("Pending verification")} + : domain.provisioningError + ? domain.provisioningError + : __("Pending verification")}
+ {domain.provisioningError && ( +
+

{__("Provisioning error")}

+

{domain.provisioningError}

+
+ )} +

{__("DNS Configuration")}

{__( diff --git a/pkg/certmanager/provisioner.go b/pkg/certmanager/provisioner.go index 60ba3df42..d287e6c76 100644 --- a/pkg/certmanager/provisioner.go +++ b/pkg/certmanager/provisioner.go @@ -298,6 +298,7 @@ func (p *Provisioner) resetStaleDomain( fullDomain.HTTPChallengeKeyAuth = nil fullDomain.HTTPChallengeURL = nil fullDomain.HTTPOrderURL = nil + fullDomain.ProvisioningError = nil fullDomain.SSLStatus = coredata.CustomDomainSSLStatusPending if fullDomain.SSLLastAttemptAt != nil && time.Since(*fullDomain.SSLLastAttemptAt) > 24*time.Hour { @@ -341,7 +342,13 @@ func (p *Provisioner) provisionDomainCertificate( log.Error(err), ) - return err + errMsg := err.Error() + domain.ProvisioningError = &errMsg + if err := domain.Update(ctx, tx, coredata.NewNoScope()); err != nil { + return fmt.Errorf("cannot update domain with provisioning error: %w", err) + } + + return nil } if err := p.checkCAARecords(domain.Domain); err != nil { @@ -352,9 +359,17 @@ func (p *Provisioner) provisionDomainCertificate( log.Error(err), ) - return err + errMsg := err.Error() + domain.ProvisioningError = &errMsg + if err := domain.Update(ctx, tx, coredata.NewNoScope()); err != nil { + return fmt.Errorf("cannot update domain with provisioning error: %w", err) + } + + return nil } + domain.ProvisioningError = nil + p.logger.InfoCtx(ctx, "DNS configuration verified, initiating HTTP challenge for domain", log.String("domain", domain.Domain)) challenge, err := p.acmeService.GetHTTPChallenge(ctx, domain.Domain) @@ -406,6 +421,8 @@ func (p *Provisioner) provisionDomainCertificate( log.Error(err), ) + errMsg := err.Error() + domain.ProvisioningError = &errMsg domain.SSLRetryCount = domain.SSLRetryCount + 1 domain.SSLLastAttemptAt = new(time.Now()) @@ -445,6 +462,7 @@ func (p *Provisioner) provisionDomainCertificate( log.Time("expires_at", cert.ExpiresAt), ) + domain.ProvisioningError = nil domain.SSLCertificatePEM = cert.CertPEM if err := domain.EncryptPrivateKey(cert.KeyPEM, p.encryptionKey); err != nil { return fmt.Errorf("cannot encrypt private key: %w", err) diff --git a/pkg/coredata/custom_domain.go b/pkg/coredata/custom_domain.go index c8b20c9ac..12b63ca61 100644 --- a/pkg/coredata/custom_domain.go +++ b/pkg/coredata/custom_domain.go @@ -47,6 +47,7 @@ type ( SSLExpiresAt *time.Time `db:"ssl_expires_at"` SSLRetryCount int `db:"ssl_retry_count"` SSLLastAttemptAt *time.Time `db:"ssl_last_attempt_at"` + ProvisioningError *string `db:"provisioning_error"` CreatedAt time.Time `db:"created_at"` UpdatedAt time.Time `db:"updated_at"` } @@ -171,6 +172,7 @@ SELECT ssl_expires_at, ssl_retry_count, ssl_last_attempt_at, + provisioning_error, created_at, updated_at FROM @@ -227,6 +229,7 @@ SELECT ssl_expires_at, ssl_retry_count, ssl_last_attempt_at, + provisioning_error, created_at, updated_at FROM @@ -283,6 +286,7 @@ SELECT ssl_expires_at, ssl_retry_count, ssl_last_attempt_at, + provisioning_error, created_at, updated_at FROM @@ -339,6 +343,7 @@ SELECT ssl_expires_at, ssl_retry_count, ssl_last_attempt_at, + provisioning_error, created_at, updated_at FROM @@ -401,6 +406,7 @@ INSERT INTO custom_domains ( ssl_expires_at, ssl_retry_count, ssl_last_attempt_at, + provisioning_error, created_at, updated_at ) VALUES ( @@ -419,6 +425,7 @@ INSERT INTO custom_domains ( @ssl_expires_at, @ssl_retry_count, @ssl_last_attempt_at, + @provisioning_error, @created_at, @updated_at ) @@ -440,6 +447,7 @@ INSERT INTO custom_domains ( "ssl_expires_at": cd.SSLExpiresAt, "ssl_retry_count": cd.SSLRetryCount, "ssl_last_attempt_at": cd.SSLLastAttemptAt, + "provisioning_error": cd.ProvisioningError, "created_at": cd.CreatedAt, "updated_at": cd.UpdatedAt, } @@ -485,6 +493,7 @@ SET ssl_expires_at = @ssl_expires_at, ssl_retry_count = @ssl_retry_count, ssl_last_attempt_at = @ssl_last_attempt_at, + provisioning_error = @provisioning_error, updated_at = @updated_at WHERE %s @@ -506,6 +515,7 @@ WHERE "ssl_expires_at": cd.SSLExpiresAt, "ssl_retry_count": cd.SSLRetryCount, "ssl_last_attempt_at": cd.SSLLastAttemptAt, + "provisioning_error": cd.ProvisioningError, "updated_at": time.Now(), } maps.Copy(args, scope.SQLArguments()) @@ -567,6 +577,7 @@ SELECT ssl_expires_at, ssl_retry_count, ssl_last_attempt_at, + provisioning_error, created_at, updated_at FROM @@ -618,6 +629,7 @@ SELECT ssl_expires_at, ssl_retry_count, ssl_last_attempt_at, + provisioning_error, created_at, updated_at FROM @@ -670,6 +682,7 @@ SELECT ssl_expires_at, ssl_retry_count, ssl_last_attempt_at, + provisioning_error, created_at, updated_at FROM @@ -725,6 +738,7 @@ SELECT ssl_expires_at, ssl_retry_count, ssl_last_attempt_at, + provisioning_error, created_at, updated_at FROM @@ -775,6 +789,7 @@ SELECT ssl_expires_at, ssl_retry_count, ssl_last_attempt_at, + provisioning_error, created_at, updated_at FROM diff --git a/pkg/coredata/migrations/20260423T074540Z.sql b/pkg/coredata/migrations/20260423T074540Z.sql new file mode 100644 index 000000000..0c90b539a --- /dev/null +++ b/pkg/coredata/migrations/20260423T074540Z.sql @@ -0,0 +1,15 @@ +-- Copyright (c) 2026 Probo Inc . +-- +-- Permission to use, copy, modify, and/or distribute this software for any +-- purpose with or without fee is hereby granted, provided that the above +-- copyright notice and this permission notice appear in all copies. +-- +-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +-- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +-- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +-- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +-- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +-- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +-- PERFORMANCE OF THIS SOFTWARE. + +ALTER TABLE custom_domains ADD COLUMN provisioning_error TEXT; diff --git a/pkg/server/api/console/v1/graphql/trust_center.graphql b/pkg/server/api/console/v1/graphql/trust_center.graphql index 510b97955..acebb6c9d 100644 --- a/pkg/server/api/console/v1/graphql/trust_center.graphql +++ b/pkg/server/api/console/v1/graphql/trust_center.graphql @@ -471,6 +471,7 @@ type CustomDomain implements Node { domain: String! sslStatus: SSLStatus! sslExpiresAt: Datetime + provisioningError: String dnsRecords: [DNSRecordInstruction!]! createdAt: Datetime! updatedAt: Datetime! diff --git a/pkg/server/api/console/v1/types/custom_domain.go b/pkg/server/api/console/v1/types/custom_domain.go index 933c22694..028b79cdc 100644 --- a/pkg/server/api/console/v1/types/custom_domain.go +++ b/pkg/server/api/console/v1/types/custom_domain.go @@ -24,11 +24,12 @@ func NewCustomDomain(d *coredata.CustomDomain, cnameTarget string) *CustomDomain Organization: &Organization{ ID: d.OrganizationID, }, - Domain: d.Domain, - SslStatus: d.SSLStatus, - CreatedAt: d.CreatedAt, - UpdatedAt: d.UpdatedAt, - SslExpiresAt: d.SSLExpiresAt, + Domain: d.Domain, + SslStatus: d.SSLStatus, + CreatedAt: d.CreatedAt, + UpdatedAt: d.UpdatedAt, + SslExpiresAt: d.SSLExpiresAt, + ProvisioningError: d.ProvisioningError, } // Convert DNS records