diff --git a/api/bindings/v1alpha1/boundendpoint_conditions.go b/api/bindings/v1alpha1/boundendpoint_conditions.go new file mode 100644 index 00000000..5fc38330 --- /dev/null +++ b/api/bindings/v1alpha1/boundendpoint_conditions.go @@ -0,0 +1,69 @@ +package v1alpha1 + +// BoundEndpointConditionType is a type of condition for a BoundEndpoint. +type BoundEndpointConditionType string + +// BoundEndpointConditionReadyReason is a reason for the Ready condition on a BoundEndpoint. +type BoundEndpointConditionReadyReason string + +// BoundEndpointConditionServicesCreatedReason is a reason for the ServicesCreated condition on a BoundEndpoint. +type BoundEndpointConditionServicesCreatedReason string + +// BoundEndpointConditionConnectivityVerifiedReason is a reason for the ConnectivityVerified condition on a BoundEndpoint. +type BoundEndpointConditionConnectivityVerifiedReason string + +const ( + // BoundEndpointConditionReady indicates whether the BoundEndpoint is fully ready + // and all required Kubernetes services have been created and connectivity has been verified. + // This condition will be True when both ServicesCreated and ConnectivityVerified are True. + BoundEndpointConditionReady BoundEndpointConditionType = "Ready" + + // BoundEndpointConditionServicesCreated indicates whether all required Kubernetes + // services for the BoundEndpoint have been successfully created. + // This condition will be True when service creation completes successfully, + // and False if service creation fails. + BoundEndpointConditionServicesCreated BoundEndpointConditionType = "ServicesCreated" + + // BoundEndpointConditionConnectivityVerified indicates whether connectivity + // to the bound endpoint has been successfully verified. + // This condition will be True when connectivity checks pass, + // and False if connectivity verification fails. + BoundEndpointConditionConnectivityVerified BoundEndpointConditionType = "ConnectivityVerified" +) + +// Reasons for Ready condition +const ( + // BoundEndpointReasonReady is used when the BoundEndpoint is fully ready, + // with all services created and connectivity verified. + BoundEndpointReasonReady BoundEndpointConditionReadyReason = "BoundEndpointReady" + + // BoundEndpointReasonServicesNotCreated is used when the Ready condition is False + // because required Kubernetes services have not been created yet. + BoundEndpointReasonServicesNotCreated BoundEndpointConditionReadyReason = "ServicesNotCreated" + + // BoundEndpointReasonConnectivityNotVerified is used when the Ready condition is False + // because connectivity to the bound endpoint has not been verified yet. + BoundEndpointReasonConnectivityNotVerified BoundEndpointConditionReadyReason = "ConnectivityNotVerified" +) + +// Reasons for ServicesCreated condition +const ( + // BoundEndpointReasonServicesCreated is used when all required Kubernetes services + // have been successfully created for the BoundEndpoint. + BoundEndpointReasonServicesCreated BoundEndpointConditionServicesCreatedReason = "ServicesCreated" + + // BoundEndpointReasonServiceCreationFailed is used when the controller fails to create + // one or more required Kubernetes services for the BoundEndpoint. + BoundEndpointReasonServiceCreationFailed BoundEndpointConditionServicesCreatedReason = "ServiceCreationFailed" +) + +// Reasons for ConnectivityVerified condition +const ( + // BoundEndpointReasonConnectivityVerified is used when connectivity to the + // BoundEndpoint has been successfully verified. + BoundEndpointReasonConnectivityVerified BoundEndpointConditionConnectivityVerifiedReason = "ConnectivityVerified" + + // BoundEndpointReasonConnectivityFailed is used when connectivity verification + // to the BoundEndpoint fails. + BoundEndpointReasonConnectivityFailed BoundEndpointConditionConnectivityVerifiedReason = "ConnectivityFailed" +) diff --git a/api/ingress/v1alpha1/domain_conditions.go b/api/ingress/v1alpha1/domain_conditions.go new file mode 100644 index 00000000..7e26d512 --- /dev/null +++ b/api/ingress/v1alpha1/domain_conditions.go @@ -0,0 +1,133 @@ +package v1alpha1 + +// DomainConditionType is a type of condition for a Domain. +type DomainConditionType string + +// DomainConditionReadyReason is a reason for the Ready condition on a Domain. +type DomainConditionReadyReason string + +// DomainConditionCreatedReason is a reason for the DomainCreated condition on a Domain. +type DomainConditionCreatedReason string + +// DomainConditionCertificateReadyReason is a reason for the CertificateReady condition on a Domain. +type DomainConditionCertificateReadyReason string + +// DomainConditionDNSConfiguredReason is a reason for the DNSConfigured condition on a Domain. +type DomainConditionDNSConfiguredReason string + +// DomainConditionProgressingReason is a reason for the Progressing condition on a Domain. +type DomainConditionProgressingReason string + +const ( + // DomainConditionReady indicates whether the Domain is fully ready for use, + // with certificate provisioning and DNS configuration complete. + // For ngrok-managed domains, this will be True immediately upon creation. + // For custom domains, this will be True when both certificate and DNS are ready. + DomainConditionReady DomainConditionType = "Ready" + + // DomainConditionCreated indicates whether the domain has been successfully + // created or registered via the ngrok API. + // This condition will be True when domain creation succeeds, + // and False if domain creation fails or the domain specification is invalid. + DomainConditionCreated DomainConditionType = "DomainCreated" + + // DomainConditionCertificateReady indicates whether the TLS certificate for + // the domain has been successfully provisioned. + // For ngrok-managed domains, this is always True. + // For custom domains, this tracks the certificate provisioning process. + DomainConditionCertificateReady DomainConditionType = "CertificateReady" + + // DomainConditionDNSConfigured indicates whether DNS configuration for the + // domain is complete and correctly pointing to ngrok. + // For ngrok-managed domains, this is always True. + // For custom domains, this tracks DNS setup status. + DomainConditionDNSConfigured DomainConditionType = "DNSConfigured" + + // DomainConditionProgressing indicates whether the domain is currently being + // provisioned, with setup steps in progress. + // This condition will be True during active provisioning operations. + DomainConditionProgressing DomainConditionType = "Progressing" +) + +// Reasons for Ready condition +const ( + // DomainReasonActive is used when the Domain is fully active and ready for use, + // with all provisioning steps complete. + DomainReasonActive DomainConditionReadyReason = "DomainActive" + + // DomainReasonInvalid is used when the Domain specification is invalid + // (e.g., invalid domain name format). + DomainReasonInvalid DomainConditionReadyReason = "DomainInvalid" + + // DomainReasonCreationFailed is used when the Domain creation failed via the ngrok API. + DomainReasonCreationFailed DomainConditionReadyReason = "DomainCreationFailed" + + // DomainReasonProvisioningError is used when there is an error during the + // domain provisioning process (certificate or DNS). + DomainReasonProvisioningError DomainConditionReadyReason = "ProvisioningError" +) + +// Reasons for DomainCreated condition +const ( + // DomainCreatedReasonCreated is used when the domain has been successfully created + // or registered via the ngrok API. + DomainCreatedReasonCreated DomainConditionCreatedReason = "DomainCreated" + + // DomainCreatedReasonCreationFailed is used when the domain creation failed via the ngrok API. + DomainCreatedReasonCreationFailed DomainConditionCreatedReason = "DomainCreationFailed" + + // DomainCreatedReasonInvalid is used when the Domain specification is invalid. + DomainCreatedReasonInvalid DomainConditionCreatedReason = "DomainInvalid" +) + +// Reasons for CertificateReady condition +const ( + // DomainCertificateReadyReasonReady is used when the TLS certificate has been + // successfully provisioned for the domain. + DomainCertificateReadyReasonReady DomainConditionCertificateReadyReason = "CertificateReady" + + // DomainCertificateReadyReasonNgrokManaged is used when the domain is ngrok-managed and + // certificate management is handled automatically by ngrok. + DomainCertificateReadyReasonNgrokManaged DomainConditionCertificateReadyReason = "NgrokManaged" + + // DomainCertificateReadyReasonProvisioningError is used when there is an error provisioning + // the TLS certificate for the domain. + DomainCertificateReadyReasonProvisioningError DomainConditionCertificateReadyReason = "ProvisioningError" + + // DomainCertificateReadyReasonCreationFailed is used when certificate provisioning cannot proceed + // because the domain creation failed. + DomainCertificateReadyReasonCreationFailed DomainConditionCertificateReadyReason = "DomainCreationFailed" + + // DomainCertificateReadyReasonInvalid is used when certificate provisioning cannot proceed + // because the domain specification is invalid. + DomainCertificateReadyReasonInvalid DomainConditionCertificateReadyReason = "DomainInvalid" +) + +// Reasons for DNSConfigured condition +const ( + // DomainDNSConfiguredReasonConfigured is used when DNS is properly configured for the domain. + DomainDNSConfiguredReasonConfigured DomainConditionDNSConfiguredReason = "DomainCreated" + + // DomainDNSConfiguredReasonNgrokManaged is used when the domain is ngrok-managed and + // DNS is automatically configured by ngrok. + DomainDNSConfiguredReasonNgrokManaged DomainConditionDNSConfiguredReason = "NgrokManaged" + + // DomainDNSConfiguredReasonProvisioningError is used when there is an error with DNS + // configuration for the domain. + DomainDNSConfiguredReasonProvisioningError DomainConditionDNSConfiguredReason = "ProvisioningError" + + // DomainDNSConfiguredReasonCreationFailed is used when DNS configuration cannot proceed + // because the domain creation failed. + DomainDNSConfiguredReasonCreationFailed DomainConditionDNSConfiguredReason = "DomainCreationFailed" + + // DomainDNSConfiguredReasonInvalid is used when DNS configuration cannot proceed + // because the domain specification is invalid. + DomainDNSConfiguredReasonInvalid DomainConditionDNSConfiguredReason = "DomainInvalid" +) + +// Reasons for Progressing condition +const ( + // DomainReasonProvisioning is used when the domain is actively being provisioned, + // with certificate or DNS setup in progress. + DomainReasonProvisioning DomainConditionProgressingReason = "Provisioning" +) diff --git a/api/ingress/v1alpha1/ippolicy_conditions.go b/api/ingress/v1alpha1/ippolicy_conditions.go new file mode 100644 index 00000000..7f8b4dd0 --- /dev/null +++ b/api/ingress/v1alpha1/ippolicy_conditions.go @@ -0,0 +1,77 @@ +package v1alpha1 + +// IPPolicyConditionType is a type of condition for an IPPolicy. +type IPPolicyConditionType string + +// IPPolicyConditionReadyReason is a reason for the Ready condition on an IPPolicy. +type IPPolicyConditionReadyReason string + +// IPPolicyConditionCreatedReason is a reason for the IPPolicyCreated condition on an IPPolicy. +type IPPolicyConditionCreatedReason string + +// IPPolicyConditionRulesConfiguredReason is a reason for the RulesConfigured condition on an IPPolicy. +type IPPolicyConditionRulesConfiguredReason string + +const ( + // IPPolicyConditionReady indicates whether the IPPolicy is fully ready and active, + // with the policy created and all rules properly configured. + // This condition will be True when both IPPolicyCreated and RulesConfigured are True. + IPPolicyConditionReady IPPolicyConditionType = "Ready" + + // IPPolicyConditionCreated indicates whether the IP policy has been successfully + // created via the ngrok API. + // This condition will be True when policy creation succeeds, + // and False if policy creation fails. + IPPolicyConditionCreated IPPolicyConditionType = "IPPolicyCreated" + + // IPPolicyConditionRulesConfigured indicates whether all IP policy rules have + // been successfully configured and applied. + // This condition will be True when rule configuration succeeds, + // and False if there are errors in the rule configuration (e.g., invalid CIDR). + IPPolicyConditionRulesConfigured IPPolicyConditionType = "RulesConfigured" +) + +// Reasons for Ready condition +const ( + // IPPolicyReasonActive is used when the IPPolicy is fully active and ready, + // with the policy created and all rules configured. + IPPolicyReasonActive IPPolicyConditionReadyReason = "IPPolicyActive" + + // IPPolicyReasonRulesConfigurationError is used when the Ready condition is False + // because there are errors in the IP policy rules configuration. + IPPolicyReasonRulesConfigurationError IPPolicyConditionReadyReason = "IPPolicyRulesConfigurationError" + + // IPPolicyReasonCreationFailed is used when the Ready condition is False + // because the IP policy could not be created. + IPPolicyReasonCreationFailed IPPolicyConditionReadyReason = "IPPolicyCreationFailed" + + // IPPolicyReasonInvalidCIDR is used when the Ready condition is False + // because one or more IP policy rules contain an invalid CIDR block specification. + IPPolicyReasonInvalidCIDR IPPolicyConditionReadyReason = "IPPolicyInvalidCIDR" +) + +// Reasons for IPPolicyCreated condition +const ( + // IPPolicyCreatedReasonCreated is used when the IP policy has been successfully + // created via the ngrok API. + IPPolicyCreatedReasonCreated IPPolicyConditionCreatedReason = "IPPolicyCreated" + + // IPPolicyCreatedReasonCreationFailed is used when the controller fails to create + // the IP policy via the ngrok API. + IPPolicyCreatedReasonCreationFailed IPPolicyConditionCreatedReason = "IPPolicyCreationFailed" +) + +// Reasons for RulesConfigured condition +const ( + // IPPolicyRulesConfiguredReasonConfigured is used when all IP policy rules have been + // successfully configured and applied. + IPPolicyRulesConfiguredReasonConfigured IPPolicyConditionRulesConfiguredReason = "IPPolicyRulesConfigured" + + // IPPolicyRulesConfiguredReasonConfigurationError is used when there are errors + // configuring the IP policy rules. + IPPolicyRulesConfiguredReasonConfigurationError IPPolicyConditionRulesConfiguredReason = "IPPolicyRulesConfigurationError" + + // IPPolicyRulesConfiguredReasonInvalidCIDR is used when one or more IP policy rules contain + // an invalid CIDR block specification. + IPPolicyRulesConfiguredReasonInvalidCIDR IPPolicyConditionRulesConfiguredReason = "IPPolicyInvalidCIDR" +) diff --git a/api/ngrok/v1alpha1/agentendpoint_conditions.go b/api/ngrok/v1alpha1/agentendpoint_conditions.go new file mode 100644 index 00000000..9220b7c0 --- /dev/null +++ b/api/ngrok/v1alpha1/agentendpoint_conditions.go @@ -0,0 +1,84 @@ +package v1alpha1 + +// AgentEndpointConditionType is a type of condition for an AgentEndpoint. +type AgentEndpointConditionType string + +// AgentEndpointConditionReadyReason is a reason for the Ready condition on an AgentEndpoint. +type AgentEndpointConditionReadyReason string + +// AgentEndpointConditionEndpointCreatedReason is a reason for the EndpointCreated condition on an AgentEndpoint. +type AgentEndpointConditionEndpointCreatedReason string + +// AgentEndpointConditionTrafficPolicyReason is a reason for the TrafficPolicyApplied condition on an AgentEndpoint. +type AgentEndpointConditionTrafficPolicyReason string + +const ( + // AgentEndpointConditionReady indicates whether the AgentEndpoint is fully ready + // and active, with the endpoint created and any traffic policies applied. + // This condition will be True when the endpoint is active and healthy. + AgentEndpointConditionReady AgentEndpointConditionType = "Ready" + + // AgentEndpointConditionEndpointCreated indicates whether the ngrok endpoint + // has been successfully created via the ngrok API. + // This condition will be True when endpoint creation succeeds, + // and False if endpoint creation fails. + AgentEndpointConditionEndpointCreated AgentEndpointConditionType = "EndpointCreated" + + // AgentEndpointConditionTrafficPolicy indicates whether any configured traffic + // policies have been successfully applied to the endpoint. + // This condition will be True when traffic policy application succeeds, + // and False if there are errors applying the policy. + AgentEndpointConditionTrafficPolicy AgentEndpointConditionType = "TrafficPolicyApplied" +) + +// Reasons for Ready condition +const ( + // AgentEndpointReasonActive is used when the AgentEndpoint is fully active + // and ready to serve traffic. + AgentEndpointReasonActive AgentEndpointConditionReadyReason = "EndpointActive" + + // AgentEndpointReasonReconciling is used when the AgentEndpoint is currently + // being reconciled and is not yet ready. + AgentEndpointReasonReconciling AgentEndpointConditionReadyReason = "Reconciling" + + // AgentEndpointReasonPending is used when the AgentEndpoint creation is pending, + // waiting for dependencies or preconditions to be met. + AgentEndpointReasonPending AgentEndpointConditionReadyReason = "Pending" + + // AgentEndpointReasonUnknown is used when the AgentEndpoint status cannot be determined. + AgentEndpointReasonUnknown AgentEndpointConditionReadyReason = "Unknown" + + // AgentEndpointReasonDomainNotReady is used when the AgentEndpoint is not ready + // because a referenced Domain resource is not yet ready. + AgentEndpointReasonDomainNotReady AgentEndpointConditionReadyReason = "DomainNotReady" +) + +// Reasons for EndpointCreated condition +const ( + // AgentEndpointReasonEndpointCreated is used when the ngrok endpoint has been + // successfully created via the ngrok API. + AgentEndpointReasonEndpointCreated AgentEndpointConditionEndpointCreatedReason = "EndpointCreated" + + // AgentEndpointReasonNgrokAPIError is used when there is an error communicating + // with the ngrok API to create the endpoint. + AgentEndpointReasonNgrokAPIError AgentEndpointConditionEndpointCreatedReason = "NgrokAPIError" + + // AgentEndpointReasonConfigError is used when the AgentEndpoint configuration + // is invalid or incomplete. + AgentEndpointReasonConfigError AgentEndpointConditionEndpointCreatedReason = "ConfigurationError" + + // AgentEndpointReasonUpstreamError is used when there is an error with the + // upstream service configuration or connectivity. + AgentEndpointReasonUpstreamError AgentEndpointConditionEndpointCreatedReason = "UpstreamError" +) + +// Reasons for TrafficPolicyApplied condition +const ( + // AgentEndpointReasonTrafficPolicyApplied is used when configured traffic policies + // have been successfully applied to the endpoint. + AgentEndpointReasonTrafficPolicyApplied AgentEndpointConditionTrafficPolicyReason = "TrafficPolicyApplied" + + // AgentEndpointReasonTrafficPolicyError is used when there is an error applying + // traffic policies to the endpoint. + AgentEndpointReasonTrafficPolicyError AgentEndpointConditionTrafficPolicyReason = "TrafficPolicyError" +) diff --git a/api/ngrok/v1alpha1/cloudendpoint_conditions.go b/api/ngrok/v1alpha1/cloudendpoint_conditions.go new file mode 100644 index 00000000..e0be3744 --- /dev/null +++ b/api/ngrok/v1alpha1/cloudendpoint_conditions.go @@ -0,0 +1,52 @@ +package v1alpha1 + +// CloudEndpointConditionType is a type of condition for a CloudEndpoint. +type CloudEndpointConditionType string + +// CloudEndpointConditionReadyReason is a reason for the Ready condition on a CloudEndpoint. +type CloudEndpointConditionReadyReason string + +// CloudEndpointConditionCreatedReason is a reason for the CloudEndpointCreated condition on a CloudEndpoint. +type CloudEndpointConditionCreatedReason string + +const ( + // CloudEndpointConditionReady indicates whether the CloudEndpoint is fully ready + // and active in the ngrok cloud. + // This condition will be True when the cloud endpoint is active and available. + CloudEndpointConditionReady CloudEndpointConditionType = "Ready" + + // CloudEndpointConditionCreated indicates whether the cloud endpoint has been + // successfully created via the ngrok API. + // This condition will be True when endpoint creation succeeds, + // and False if endpoint creation fails. + CloudEndpointConditionCreated CloudEndpointConditionType = "CloudEndpointCreated" +) + +// Reasons for Ready condition +const ( + // CloudEndpointReasonActive is used when the CloudEndpoint is fully active + // and ready in the ngrok cloud. + CloudEndpointReasonActive CloudEndpointConditionReadyReason = "CloudEndpointActive" + + // CloudEndpointReasonPending is used when the CloudEndpoint creation is pending, + // waiting for dependencies or preconditions to be met. + CloudEndpointReasonPending CloudEndpointConditionReadyReason = "Pending" + + // CloudEndpointReasonUnknown is used when the CloudEndpoint status cannot be determined. + CloudEndpointReasonUnknown CloudEndpointConditionReadyReason = "Unknown" + + // CloudEndpointReasonDomainNotReady is used when the CloudEndpoint is not ready + // because a referenced Domain resource is not yet ready. + CloudEndpointReasonDomainNotReady CloudEndpointConditionReadyReason = "DomainNotReady" +) + +// Reasons for CloudEndpointCreated condition +const ( + // CloudEndpointReasonCreated is used when the cloud endpoint has been + // successfully created via the ngrok API. + CloudEndpointReasonCreated CloudEndpointConditionCreatedReason = "CloudEndpointCreated" + + // CloudEndpointReasonCreationFailed is used when the controller fails to create + // the cloud endpoint via the ngrok API. + CloudEndpointReasonCreationFailed CloudEndpointConditionCreatedReason = "CloudEndpointCreationFailed" +) diff --git a/internal/controller/agent/agent_endpoint_conditions.go b/internal/controller/agent/agent_endpoint_conditions.go index 5b2f2d96..98615442 100644 --- a/internal/controller/agent/agent_endpoint_conditions.go +++ b/internal/controller/agent/agent_endpoint_conditions.go @@ -7,35 +7,17 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// Standard condition types for AgentEndpoint -const ( - ConditionReady = "Ready" - ConditionEndpointCreated = "EndpointCreated" - ConditionTrafficPolicy = "TrafficPolicyApplied" -) - -// Standard condition reasons -const ( - ReasonEndpointActive = "EndpointActive" - ReasonTrafficPolicyError = "TrafficPolicyError" - ReasonNgrokAPIError = "NgrokAPIError" - ReasonUpstreamError = "UpstreamError" - ReasonEndpointCreated = "EndpointCreated" - ReasonConfigError = "ConfigurationError" - ReasonReconciling = "Reconciling" -) - // setReadyCondition sets the Ready condition based on the overall endpoint state -func setReadyCondition(endpoint *ngrokv1alpha1.AgentEndpoint, ready bool, reason, message string) { +func setReadyCondition(endpoint *ngrokv1alpha1.AgentEndpoint, ready bool, reason ngrokv1alpha1.AgentEndpointConditionReadyReason, message string) { status := metav1.ConditionTrue if !ready { status = metav1.ConditionFalse } condition := metav1.Condition{ - Type: ConditionReady, + Type: string(ngrokv1alpha1.AgentEndpointConditionReady), Status: status, - Reason: reason, + Reason: string(reason), Message: message, ObservedGeneration: endpoint.Generation, } @@ -44,16 +26,16 @@ func setReadyCondition(endpoint *ngrokv1alpha1.AgentEndpoint, ready bool, reason } // setEndpointCreatedCondition sets the EndpointCreated condition -func setEndpointCreatedCondition(endpoint *ngrokv1alpha1.AgentEndpoint, created bool, reason, message string) { +func setEndpointCreatedCondition(endpoint *ngrokv1alpha1.AgentEndpoint, created bool, reason ngrokv1alpha1.AgentEndpointConditionEndpointCreatedReason, message string) { status := metav1.ConditionTrue if !created { status = metav1.ConditionFalse } condition := metav1.Condition{ - Type: ConditionEndpointCreated, + Type: string(ngrokv1alpha1.AgentEndpointConditionEndpointCreated), Status: status, - Reason: reason, + Reason: string(reason), Message: message, ObservedGeneration: endpoint.Generation, } @@ -62,16 +44,16 @@ func setEndpointCreatedCondition(endpoint *ngrokv1alpha1.AgentEndpoint, created } // setTrafficPolicyCondition sets the TrafficPolicyApplied condition -func setTrafficPolicyCondition(endpoint *ngrokv1alpha1.AgentEndpoint, applied bool, reason, message string) { +func setTrafficPolicyCondition(endpoint *ngrokv1alpha1.AgentEndpoint, applied bool, reason ngrokv1alpha1.AgentEndpointConditionTrafficPolicyReason, message string) { status := metav1.ConditionTrue if !applied { status = metav1.ConditionFalse } condition := metav1.Condition{ - Type: ConditionTrafficPolicy, + Type: string(ngrokv1alpha1.AgentEndpointConditionTrafficPolicy), Status: status, - Reason: reason, + Reason: string(reason), Message: message, ObservedGeneration: endpoint.Generation, } @@ -81,16 +63,16 @@ func setTrafficPolicyCondition(endpoint *ngrokv1alpha1.AgentEndpoint, applied bo // setReconcilingCondition sets a temporary reconciling condition func setReconcilingCondition(endpoint *ngrokv1alpha1.AgentEndpoint, message string) { - setReadyCondition(endpoint, false, ReasonReconciling, message) + setReadyCondition(endpoint, false, ngrokv1alpha1.AgentEndpointReasonReconciling, message) } // calculateAgentEndpointReadyCondition calculates the overall Ready condition based on other conditions and domain status func calculateAgentEndpointReadyCondition(aep *ngrokv1alpha1.AgentEndpoint, domainResult *domainpkg.DomainResult) { // Check all required conditions - endpointCreatedCondition := meta.FindStatusCondition(aep.Status.Conditions, ConditionEndpointCreated) + endpointCreatedCondition := meta.FindStatusCondition(aep.Status.Conditions, string(ngrokv1alpha1.AgentEndpointConditionEndpointCreated)) endpointCreated := endpointCreatedCondition != nil && endpointCreatedCondition.Status == metav1.ConditionTrue - trafficPolicyCondition := meta.FindStatusCondition(aep.Status.Conditions, ConditionTrafficPolicy) + trafficPolicyCondition := meta.FindStatusCondition(aep.Status.Conditions, string(ngrokv1alpha1.AgentEndpointConditionTrafficPolicy)) trafficPolicyReady := true // If traffic policy condition exists and is False, it's not ready if trafficPolicyCondition != nil && trafficPolicyCondition.Status == metav1.ConditionFalse { @@ -104,35 +86,36 @@ func calculateAgentEndpointReadyCondition(aep *ngrokv1alpha1.AgentEndpoint, doma ready := endpointCreated && trafficPolicyReady && domainReady // Determine reason and message based on state - var reason, message string + var reason ngrokv1alpha1.AgentEndpointConditionReadyReason + var message string switch { case ready: - reason = ReasonEndpointActive + reason = ngrokv1alpha1.AgentEndpointReasonActive message = "AgentEndpoint is active and ready" case !domainReady: // Use the domain's Ready condition reason/message for better context if domainResult.ReadyReason != "" { - reason = domainResult.ReadyReason + reason = ngrokv1alpha1.AgentEndpointConditionReadyReason(domainResult.ReadyReason) message = domainResult.ReadyMessage } else { - reason = "DomainNotReady" + reason = ngrokv1alpha1.AgentEndpointReasonDomainNotReady message = "Domain is not ready" } case !endpointCreated: // If EndpointCreated condition exists and is False, use its reason/message if endpointCreatedCondition != nil && endpointCreatedCondition.Status == metav1.ConditionFalse { - reason = endpointCreatedCondition.Reason + reason = ngrokv1alpha1.AgentEndpointConditionReadyReason(endpointCreatedCondition.Reason) message = endpointCreatedCondition.Message } else { - reason = "Pending" + reason = ngrokv1alpha1.AgentEndpointReasonPending message = "Waiting for endpoint creation" } case !trafficPolicyReady: // Use the traffic policy's condition reason/message - reason = trafficPolicyCondition.Reason + reason = ngrokv1alpha1.AgentEndpointConditionReadyReason(trafficPolicyCondition.Reason) message = trafficPolicyCondition.Message default: - reason = "Unknown" + reason = ngrokv1alpha1.AgentEndpointReasonUnknown message = "AgentEndpoint is not ready" } diff --git a/internal/controller/agent/agent_endpoint_conditions_test.go b/internal/controller/agent/agent_endpoint_conditions_test.go index 12c6f947..83eeee78 100644 --- a/internal/controller/agent/agent_endpoint_conditions_test.go +++ b/internal/controller/agent/agent_endpoint_conditions_test.go @@ -4,10 +4,10 @@ import ( "testing" "github.com/stretchr/testify/assert" - "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ngrokv1alpha1 "github.com/ngrok/ngrok-operator/api/ngrok/v1alpha1" + "github.com/ngrok/ngrok-operator/internal/controller/conditions" domainpkg "github.com/ngrok/ngrok-operator/internal/domain" ) @@ -53,45 +53,45 @@ func createNotReadyDomainResult(reason, message string) *domainpkg.DomainResult func TestCalculateAgentEndpointReadyCondition_AllReady(t *testing.T) { endpoint := createTestAgentEndpointWithConditions("test-endpoint", "default", []metav1.Condition{ { - Type: ConditionEndpointCreated, + Type: string(ngrokv1alpha1.AgentEndpointConditionEndpointCreated), Status: metav1.ConditionTrue, - Reason: ReasonEndpointCreated, + Reason: string(ngrokv1alpha1.AgentEndpointReasonEndpointCreated), }, { - Type: ConditionTrafficPolicy, + Type: string(ngrokv1alpha1.AgentEndpointConditionTrafficPolicy), Status: metav1.ConditionTrue, - Reason: "TrafficPolicyApplied", + Reason: string(ngrokv1alpha1.AgentEndpointReasonTrafficPolicyApplied), }, }) domainResult := createReadyDomainResult() calculateAgentEndpointReadyCondition(endpoint, domainResult) - readyCondition := meta.FindStatusCondition(endpoint.Status.Conditions, ConditionReady) + readyCondition := conditions.FindCondition(endpoint.Status.Conditions, ngrokv1alpha1.AgentEndpointConditionReady) assert.NotNil(t, readyCondition) assert.Equal(t, metav1.ConditionTrue, readyCondition.Status) - assert.Equal(t, ReasonEndpointActive, readyCondition.Reason) + assert.Equal(t, string(ngrokv1alpha1.AgentEndpointReasonActive), readyCondition.Reason) assert.Equal(t, "AgentEndpoint is active and ready", readyCondition.Message) } func TestCalculateAgentEndpointReadyCondition_DomainNotReady(t *testing.T) { endpoint := createTestAgentEndpointWithConditions("test-endpoint", "default", []metav1.Condition{ { - Type: ConditionEndpointCreated, + Type: string(ngrokv1alpha1.AgentEndpointConditionEndpointCreated), Status: metav1.ConditionTrue, - Reason: ReasonEndpointCreated, + Reason: string(ngrokv1alpha1.AgentEndpointReasonEndpointCreated), }, { - Type: ConditionTrafficPolicy, + Type: string(ngrokv1alpha1.AgentEndpointConditionTrafficPolicy), Status: metav1.ConditionTrue, - Reason: "TrafficPolicyApplied", + Reason: string(ngrokv1alpha1.AgentEndpointReasonTrafficPolicyApplied), }, }) domainResult := createNotReadyDomainResult("ProvisioningError", "Certificate provisioning in progress") calculateAgentEndpointReadyCondition(endpoint, domainResult) - readyCondition := meta.FindStatusCondition(endpoint.Status.Conditions, ConditionReady) + readyCondition := conditions.FindCondition(endpoint.Status.Conditions, ngrokv1alpha1.AgentEndpointConditionReady) assert.NotNil(t, readyCondition) assert.Equal(t, metav1.ConditionFalse, readyCondition.Status) assert.Equal(t, "ProvisioningError", readyCondition.Reason) @@ -101,14 +101,14 @@ func TestCalculateAgentEndpointReadyCondition_DomainNotReady(t *testing.T) { func TestCalculateAgentEndpointReadyCondition_DomainNotReadyNoReason(t *testing.T) { endpoint := createTestAgentEndpointWithConditions("test-endpoint", "default", []metav1.Condition{ { - Type: ConditionEndpointCreated, + Type: string(ngrokv1alpha1.AgentEndpointConditionEndpointCreated), Status: metav1.ConditionTrue, - Reason: ReasonEndpointCreated, + Reason: string(ngrokv1alpha1.AgentEndpointReasonEndpointCreated), }, { - Type: ConditionTrafficPolicy, + Type: string(ngrokv1alpha1.AgentEndpointConditionTrafficPolicy), Status: metav1.ConditionTrue, - Reason: "TrafficPolicyApplied", + Reason: string(ngrokv1alpha1.AgentEndpointReasonTrafficPolicyApplied), }, }) domainResult := &domainpkg.DomainResult{ @@ -118,44 +118,44 @@ func TestCalculateAgentEndpointReadyCondition_DomainNotReadyNoReason(t *testing. calculateAgentEndpointReadyCondition(endpoint, domainResult) - readyCondition := meta.FindStatusCondition(endpoint.Status.Conditions, ConditionReady) + readyCondition := conditions.FindCondition(endpoint.Status.Conditions, ngrokv1alpha1.AgentEndpointConditionReady) assert.NotNil(t, readyCondition) assert.Equal(t, metav1.ConditionFalse, readyCondition.Status) - assert.Equal(t, "DomainNotReady", readyCondition.Reason) + assert.Equal(t, string(ngrokv1alpha1.AgentEndpointReasonDomainNotReady), readyCondition.Reason) assert.Equal(t, "Domain is not ready", readyCondition.Message) } func TestCalculateAgentEndpointReadyCondition_EndpointNotCreated(t *testing.T) { endpoint := createTestAgentEndpointWithConditions("test-endpoint", "default", []metav1.Condition{ { - Type: ConditionEndpointCreated, + Type: string(ngrokv1alpha1.AgentEndpointConditionEndpointCreated), Status: metav1.ConditionFalse, - Reason: ReasonNgrokAPIError, + Reason: string(ngrokv1alpha1.AgentEndpointReasonNgrokAPIError), Message: "Failed to create endpoint", }, { - Type: ConditionTrafficPolicy, + Type: string(ngrokv1alpha1.AgentEndpointConditionTrafficPolicy), Status: metav1.ConditionTrue, - Reason: "TrafficPolicyApplied", + Reason: string(ngrokv1alpha1.AgentEndpointReasonTrafficPolicyApplied), }, }) domainResult := createReadyDomainResult() calculateAgentEndpointReadyCondition(endpoint, domainResult) - readyCondition := meta.FindStatusCondition(endpoint.Status.Conditions, ConditionReady) + readyCondition := conditions.FindCondition(endpoint.Status.Conditions, ngrokv1alpha1.AgentEndpointConditionReady) assert.NotNil(t, readyCondition) assert.Equal(t, metav1.ConditionFalse, readyCondition.Status) - assert.Equal(t, ReasonNgrokAPIError, readyCondition.Reason) + assert.Equal(t, string(ngrokv1alpha1.AgentEndpointReasonNgrokAPIError), readyCondition.Reason) assert.Equal(t, "Failed to create endpoint", readyCondition.Message) } func TestCalculateAgentEndpointReadyCondition_EndpointNotCreatedNoCondition(t *testing.T) { endpoint := createTestAgentEndpointWithConditions("test-endpoint", "default", []metav1.Condition{ { - Type: ConditionTrafficPolicy, + Type: string(ngrokv1alpha1.AgentEndpointConditionTrafficPolicy), Status: metav1.ConditionTrue, - Reason: "TrafficPolicyApplied", + Reason: string(ngrokv1alpha1.AgentEndpointReasonTrafficPolicyApplied), }, // No EndpointCreated condition }) @@ -163,24 +163,24 @@ func TestCalculateAgentEndpointReadyCondition_EndpointNotCreatedNoCondition(t *t calculateAgentEndpointReadyCondition(endpoint, domainResult) - readyCondition := meta.FindStatusCondition(endpoint.Status.Conditions, ConditionReady) + readyCondition := conditions.FindCondition(endpoint.Status.Conditions, ngrokv1alpha1.AgentEndpointConditionReady) assert.NotNil(t, readyCondition) assert.Equal(t, metav1.ConditionFalse, readyCondition.Status) - assert.Equal(t, "Pending", readyCondition.Reason) + assert.Equal(t, string(ngrokv1alpha1.AgentEndpointReasonPending), readyCondition.Reason) assert.Equal(t, "Waiting for endpoint creation", readyCondition.Message) } func TestCalculateAgentEndpointReadyCondition_TrafficPolicyNotReady(t *testing.T) { endpoint := createTestAgentEndpointWithConditions("test-endpoint", "default", []metav1.Condition{ { - Type: ConditionEndpointCreated, + Type: string(ngrokv1alpha1.AgentEndpointConditionEndpointCreated), Status: metav1.ConditionTrue, - Reason: ReasonEndpointCreated, + Reason: string(ngrokv1alpha1.AgentEndpointReasonEndpointCreated), }, { - Type: ConditionTrafficPolicy, + Type: string(ngrokv1alpha1.AgentEndpointConditionTrafficPolicy), Status: metav1.ConditionFalse, - Reason: ReasonTrafficPolicyError, + Reason: string(ngrokv1alpha1.AgentEndpointReasonTrafficPolicyError), Message: "Traffic policy validation failed", }, }) @@ -188,19 +188,19 @@ func TestCalculateAgentEndpointReadyCondition_TrafficPolicyNotReady(t *testing.T calculateAgentEndpointReadyCondition(endpoint, domainResult) - readyCondition := meta.FindStatusCondition(endpoint.Status.Conditions, ConditionReady) + readyCondition := conditions.FindCondition(endpoint.Status.Conditions, ngrokv1alpha1.AgentEndpointConditionReady) assert.NotNil(t, readyCondition) assert.Equal(t, metav1.ConditionFalse, readyCondition.Status) - assert.Equal(t, ReasonTrafficPolicyError, readyCondition.Reason) + assert.Equal(t, string(ngrokv1alpha1.AgentEndpointReasonTrafficPolicyError), readyCondition.Reason) assert.Equal(t, "Traffic policy validation failed", readyCondition.Message) } func TestCalculateAgentEndpointReadyCondition_TrafficPolicyNotSet(t *testing.T) { endpoint := createTestAgentEndpointWithConditions("test-endpoint", "default", []metav1.Condition{ { - Type: ConditionEndpointCreated, + Type: string(ngrokv1alpha1.AgentEndpointConditionEndpointCreated), Status: metav1.ConditionTrue, - Reason: ReasonEndpointCreated, + Reason: string(ngrokv1alpha1.AgentEndpointReasonEndpointCreated), }, // No TrafficPolicy condition - should be considered ready }) @@ -208,10 +208,10 @@ func TestCalculateAgentEndpointReadyCondition_TrafficPolicyNotSet(t *testing.T) calculateAgentEndpointReadyCondition(endpoint, domainResult) - readyCondition := meta.FindStatusCondition(endpoint.Status.Conditions, ConditionReady) + readyCondition := conditions.FindCondition(endpoint.Status.Conditions, ngrokv1alpha1.AgentEndpointConditionReady) assert.NotNil(t, readyCondition) assert.Equal(t, metav1.ConditionTrue, readyCondition.Status) - assert.Equal(t, ReasonEndpointActive, readyCondition.Reason) + assert.Equal(t, string(ngrokv1alpha1.AgentEndpointReasonActive), readyCondition.Reason) assert.Equal(t, "AgentEndpoint is active and ready", readyCondition.Message) } @@ -219,15 +219,15 @@ func TestCalculateAgentEndpointReadyCondition_MultipleIssues(t *testing.T) { // Domain not ready should take precedence over other issues endpoint := createTestAgentEndpointWithConditions("test-endpoint", "default", []metav1.Condition{ { - Type: ConditionEndpointCreated, + Type: string(ngrokv1alpha1.AgentEndpointConditionEndpointCreated), Status: metav1.ConditionFalse, - Reason: ReasonNgrokAPIError, + Reason: string(ngrokv1alpha1.AgentEndpointReasonNgrokAPIError), Message: "Failed to create endpoint", }, { - Type: ConditionTrafficPolicy, + Type: string(ngrokv1alpha1.AgentEndpointConditionTrafficPolicy), Status: metav1.ConditionFalse, - Reason: ReasonTrafficPolicyError, + Reason: string(ngrokv1alpha1.AgentEndpointReasonTrafficPolicyError), Message: "Traffic policy validation failed", }, }) @@ -235,7 +235,7 @@ func TestCalculateAgentEndpointReadyCondition_MultipleIssues(t *testing.T) { calculateAgentEndpointReadyCondition(endpoint, domainResult) - readyCondition := meta.FindStatusCondition(endpoint.Status.Conditions, ConditionReady) + readyCondition := conditions.FindCondition(endpoint.Status.Conditions, ngrokv1alpha1.AgentEndpointConditionReady) assert.NotNil(t, readyCondition) assert.Equal(t, metav1.ConditionFalse, readyCondition.Status) assert.Equal(t, "ProvisioningError", readyCondition.Reason) diff --git a/internal/controller/agent/agent_endpoint_controller.go b/internal/controller/agent/agent_endpoint_controller.go index 8bf4309a..6ff8e2d1 100644 --- a/internal/controller/agent/agent_endpoint_controller.go +++ b/internal/controller/agent/agent_endpoint_controller.go @@ -211,7 +211,7 @@ func (r *AgentEndpointReconciler) update(ctx context.Context, endpoint *ngrokv1a clientCerts, err := r.getClientCerts(ctx, endpoint) if err != nil { - setEndpointCreatedCondition(endpoint, false, ReasonConfigError, fmt.Sprintf("Failed to get client certificates: %v", err)) + setEndpointCreatedCondition(endpoint, false, ngrokv1alpha1.AgentEndpointReasonConfigError, fmt.Sprintf("Failed to get client certificates: %v", err)) return r.updateStatus(ctx, endpoint, nil, trafficPolicy, domainResult, err) } @@ -220,18 +220,18 @@ func (r *AgentEndpointReconciler) update(ctx context.Context, endpoint *ngrokv1a result, err := r.AgentDriver.CreateAgentEndpoint(ctx, tunnelName, endpoint.Spec, trafficPolicy, clientCerts) if err != nil { // Mark the endpoint as failed creation - setEndpointCreatedCondition(endpoint, false, ReasonNgrokAPIError, fmt.Sprintf("Failed to create endpoint: %v", err)) + setEndpointCreatedCondition(endpoint, false, ngrokv1alpha1.AgentEndpointReasonNgrokAPIError, fmt.Sprintf("Failed to create endpoint: %v", err)) // If error indicates traffic policy issue, also set that condition if trafficPolicy != "" && ngrokapi.IsTrafficPolicyError(err.Error()) { - setTrafficPolicyCondition(endpoint, false, ReasonTrafficPolicyError, ngrokapi.SanitizeErrorMessage(err.Error())) + setTrafficPolicyCondition(endpoint, false, ngrokv1alpha1.AgentEndpointReasonTrafficPolicyError, ngrokapi.SanitizeErrorMessage(err.Error())) } return r.updateStatus(ctx, endpoint, nil, trafficPolicy, domainResult, err) } // Mark the endpoint as successfully created - setEndpointCreatedCondition(endpoint, true, ReasonEndpointCreated, "Endpoint successfully created") + setEndpointCreatedCondition(endpoint, true, ngrokv1alpha1.AgentEndpointReasonEndpointCreated, "Endpoint successfully created") if trafficPolicy != "" { - setTrafficPolicyCondition(endpoint, true, "TrafficPolicyApplied", "Traffic policy successfully applied") + setTrafficPolicyCondition(endpoint, true, ngrokv1alpha1.AgentEndpointReasonTrafficPolicyApplied, "Traffic policy successfully applied") } return r.updateStatus(ctx, endpoint, result, trafficPolicy, domainResult, nil) @@ -319,7 +319,7 @@ func (r *AgentEndpointReconciler) getTrafficPolicy(ctx context.Context, aep *ngr // Ensure mutually exclusive fields are not both set if aep.Spec.TrafficPolicy.Reference != nil && aep.Spec.TrafficPolicy.Inline != nil { - setTrafficPolicyCondition(aep, false, ReasonTrafficPolicyError, ErrInvalidTrafficPolicyConfig.Error()) + setTrafficPolicyCondition(aep, false, ngrokv1alpha1.AgentEndpointReasonTrafficPolicyError, ErrInvalidTrafficPolicyConfig.Error()) return "", ErrInvalidTrafficPolicyConfig } @@ -331,7 +331,7 @@ func (r *AgentEndpointReconciler) getTrafficPolicy(ctx context.Context, aep *ngr policyBytes, err := aep.Spec.TrafficPolicy.Inline.MarshalJSON() if err != nil { errMsg := fmt.Sprintf("failed to marshal inline TrafficPolicy: %v", err) - setTrafficPolicyCondition(aep, false, ReasonTrafficPolicyError, errMsg) + setTrafficPolicyCondition(aep, false, ngrokv1alpha1.AgentEndpointReasonTrafficPolicyError, errMsg) return "", errors.New(errMsg) } policy = string(policyBytes) @@ -339,7 +339,7 @@ func (r *AgentEndpointReconciler) getTrafficPolicy(ctx context.Context, aep *ngr // Right now, we only support traffic policies that are in the same namespace as the agent endpoint policy, err = r.findTrafficPolicyByName(ctx, aep.Spec.TrafficPolicy.Reference.Name, aep.Namespace) if err != nil { - setTrafficPolicyCondition(aep, false, ReasonTrafficPolicyError, err.Error()) + setTrafficPolicyCondition(aep, false, ngrokv1alpha1.AgentEndpointReasonTrafficPolicyError, err.Error()) return "", err } } diff --git a/internal/controller/agent/agent_endpoint_controller_test.go b/internal/controller/agent/agent_endpoint_controller_test.go index 4add075c..d8fea162 100644 --- a/internal/controller/agent/agent_endpoint_controller_test.go +++ b/internal/controller/agent/agent_endpoint_controller_test.go @@ -112,10 +112,10 @@ var _ = Describe("AgentEndpoint Controller", func() { g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(agentEndpoint), obj)).To(Succeed()) // Check ready condition set by running controller - cond := testutils.FindCondition(obj.Status.Conditions, ConditionReady) + cond := testutils.FindCondition(obj.Status.Conditions, string(ngrokv1alpha1.AgentEndpointConditionReady)) g.Expect(cond).NotTo(BeNil()) g.Expect(cond.Status).To(Equal(metav1.ConditionTrue)) - g.Expect(cond.Reason).To(Equal(ReasonEndpointActive)) + g.Expect(cond.Reason).To(Equal(string(ngrokv1alpha1.AgentEndpointReasonActive))) // Verify status fields set by controller g.Expect(obj.Status.AssignedURL).To(Equal("tcp://1.tcp.ngrok.io:12345")) @@ -149,7 +149,7 @@ var _ = Describe("AgentEndpoint Controller", func() { obj := &ngrokv1alpha1.AgentEndpoint{} g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(agentEndpoint), obj)).To(Succeed()) - cond := testutils.FindCondition(obj.Status.Conditions, ConditionReady) + cond := testutils.FindCondition(obj.Status.Conditions, string(ngrokv1alpha1.AgentEndpointConditionReady)) g.Expect(cond).NotTo(BeNil()) g.Expect(cond.Status).To(Equal(metav1.ConditionTrue)) g.Expect(obj.Status.AssignedURL).To(Equal("https://test.internal")) @@ -219,10 +219,10 @@ var _ = Describe("AgentEndpoint Controller", func() { g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(agentEndpoint), obj)).To(Succeed()) // Check error condition set by running controller - cond := testutils.FindCondition(obj.Status.Conditions, ConditionReady) + cond := testutils.FindCondition(obj.Status.Conditions, string(ngrokv1alpha1.AgentEndpointConditionReady)) g.Expect(cond).NotTo(BeNil()) g.Expect(cond.Status).To(Equal(metav1.ConditionFalse)) - g.Expect(cond.Reason).To(Equal(ReasonNgrokAPIError)) + g.Expect(cond.Reason).To(Equal(string(ngrokv1alpha1.AgentEndpointReasonNgrokAPIError))) }, timeout, interval).Should(Succeed()) }) @@ -304,7 +304,7 @@ var _ = Describe("AgentEndpoint Controller", func() { g.Expect(obj.Status.AttachedTrafficPolicy).To(Equal("inline")) - cond := testutils.FindCondition(obj.Status.Conditions, ConditionReady) + cond := testutils.FindCondition(obj.Status.Conditions, string(ngrokv1alpha1.AgentEndpointConditionReady)) g.Expect(cond).NotTo(BeNil()) g.Expect(cond.Status).To(Equal(metav1.ConditionTrue)) }, timeout, interval).Should(Succeed()) @@ -366,7 +366,7 @@ var _ = Describe("AgentEndpoint Controller", func() { g.Expect(obj.Status.AttachedTrafficPolicy).To(Equal("referenced-policy")) - cond := testutils.FindCondition(obj.Status.Conditions, ConditionReady) + cond := testutils.FindCondition(obj.Status.Conditions, string(ngrokv1alpha1.AgentEndpointConditionReady)) g.Expect(cond).NotTo(BeNil()) g.Expect(cond.Status).To(Equal(metav1.ConditionTrue)) }, timeout, interval).Should(Succeed()) @@ -410,10 +410,10 @@ var _ = Describe("AgentEndpoint Controller", func() { obj := &ngrokv1alpha1.AgentEndpoint{} g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(agentEndpoint), obj)).To(Succeed()) - policyCondition := testutils.FindCondition(obj.Status.Conditions, ConditionTrafficPolicy) + policyCondition := testutils.FindCondition(obj.Status.Conditions, string(ngrokv1alpha1.AgentEndpointConditionTrafficPolicy)) g.Expect(policyCondition).NotTo(BeNil()) g.Expect(policyCondition.Status).To(Equal(metav1.ConditionFalse)) - g.Expect(policyCondition.Reason).To(Equal(ReasonTrafficPolicyError)) + g.Expect(policyCondition.Reason).To(Equal(string(ngrokv1alpha1.AgentEndpointReasonTrafficPolicyError))) }, timeout, interval).Should(Succeed()) }) @@ -488,7 +488,7 @@ var _ = Describe("AgentEndpoint Controller", func() { g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(agentEndpoint), obj)).To(Succeed()) // Check ready condition - cond := testutils.FindCondition(obj.Status.Conditions, ConditionReady) + cond := testutils.FindCondition(obj.Status.Conditions, string(ngrokv1alpha1.AgentEndpointConditionReady)) g.Expect(cond).NotTo(BeNil()) g.Expect(cond.Status).To(Equal(metav1.ConditionTrue)) @@ -611,10 +611,10 @@ var _ = Describe("AgentEndpoint Controller", func() { g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(agentEndpoint), obj)).To(Succeed()) // Check ready condition is false - cond := testutils.FindCondition(obj.Status.Conditions, ConditionReady) + cond := testutils.FindCondition(obj.Status.Conditions, string(ngrokv1alpha1.AgentEndpointConditionReady)) g.Expect(cond).NotTo(BeNil()) g.Expect(cond.Status).To(Equal(metav1.ConditionFalse)) - g.Expect(cond.Reason).To(Equal(ReasonNgrokAPIError)) + g.Expect(cond.Reason).To(Equal(string(ngrokv1alpha1.AgentEndpointReasonNgrokAPIError))) }, timeout, interval).Should(Succeed()) By("Verifying the mock driver was called for this specific endpoint") @@ -703,10 +703,10 @@ var _ = Describe("AgentEndpoint Controller", func() { obj := &ngrokv1alpha1.AgentEndpoint{} g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(agentEndpoint), obj)).To(Succeed()) - cond := testutils.FindCondition(obj.Status.Conditions, ConditionReady) + cond := testutils.FindCondition(obj.Status.Conditions, string(ngrokv1alpha1.AgentEndpointConditionReady)) g.Expect(cond).NotTo(BeNil()) g.Expect(cond.Status).To(Equal(metav1.ConditionFalse)) - g.Expect(cond.Reason).To(Equal(ReasonConfigError)) + g.Expect(cond.Reason).To(Equal(string(ngrokv1alpha1.AgentEndpointReasonConfigError))) }, timeout, interval).Should(Succeed()) }) @@ -748,10 +748,10 @@ var _ = Describe("AgentEndpoint Controller", func() { obj := &ngrokv1alpha1.AgentEndpoint{} g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(agentEndpoint), obj)).To(Succeed()) - cond := testutils.FindCondition(obj.Status.Conditions, ConditionReady) + cond := testutils.FindCondition(obj.Status.Conditions, string(ngrokv1alpha1.AgentEndpointConditionReady)) g.Expect(cond).NotTo(BeNil()) g.Expect(cond.Status).To(Equal(metav1.ConditionFalse)) - g.Expect(cond.Reason).To(Equal(ReasonConfigError)) + g.Expect(cond.Reason).To(Equal(string(ngrokv1alpha1.AgentEndpointReasonConfigError))) }, timeout, interval).Should(Succeed()) }) @@ -792,10 +792,10 @@ var _ = Describe("AgentEndpoint Controller", func() { obj := &ngrokv1alpha1.AgentEndpoint{} g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(agentEndpoint), obj)).To(Succeed()) - cond := testutils.FindCondition(obj.Status.Conditions, ConditionReady) + cond := testutils.FindCondition(obj.Status.Conditions, string(ngrokv1alpha1.AgentEndpointConditionReady)) g.Expect(cond).NotTo(BeNil()) g.Expect(cond.Status).To(Equal(metav1.ConditionFalse)) - g.Expect(cond.Reason).To(Equal(ReasonConfigError)) + g.Expect(cond.Reason).To(Equal(string(ngrokv1alpha1.AgentEndpointReasonConfigError))) }, timeout, interval).Should(Succeed()) }) }) @@ -834,10 +834,10 @@ var _ = Describe("AgentEndpoint Controller", func() { g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(agentEndpoint), obj)).To(Succeed()) // Check ready condition set by running controller - cond := testutils.FindCondition(obj.Status.Conditions, ConditionReady) + cond := testutils.FindCondition(obj.Status.Conditions, string(ngrokv1alpha1.AgentEndpointConditionReady)) g.Expect(cond).NotTo(BeNil()) g.Expect(cond.Status).To(Equal(metav1.ConditionTrue)) - g.Expect(cond.Reason).To(Equal(ReasonEndpointActive)) + g.Expect(cond.Reason).To(Equal(string(ngrokv1alpha1.AgentEndpointReasonActive))) // Verify status fields set by controller g.Expect(obj.Status.AssignedURL).To(Equal("tcp://99.tcp.ngrok.io:99999")) @@ -880,10 +880,10 @@ var _ = Describe("AgentEndpoint Controller", func() { g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(agentEndpoint), obj)).To(Succeed()) // Check ready condition set by running controller - cond := testutils.FindCondition(obj.Status.Conditions, ConditionReady) + cond := testutils.FindCondition(obj.Status.Conditions, string(ngrokv1alpha1.AgentEndpointConditionReady)) g.Expect(cond).NotTo(BeNil()) g.Expect(cond.Status).To(Equal(metav1.ConditionFalse)) - g.Expect(cond.Reason).To(Equal(ReasonNgrokAPIError)) + g.Expect(cond.Reason).To(Equal(string(ngrokv1alpha1.AgentEndpointReasonNgrokAPIError))) }, timeout, interval).Should(Succeed()) }) @@ -980,7 +980,7 @@ var _ = Describe("AgentEndpoint Controller", func() { g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(agentEndpoint), obj)).To(Succeed()) // Check ready condition - cond := testutils.FindCondition(obj.Status.Conditions, ConditionReady) + cond := testutils.FindCondition(obj.Status.Conditions, string(ngrokv1alpha1.AgentEndpointConditionReady)) g.Expect(cond).NotTo(BeNil()) g.Expect(cond.Status).To(Equal(metav1.ConditionTrue)) @@ -1081,10 +1081,10 @@ cCzFoVcb6XWg4MpPeZ25v+xA g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(agentEndpoint), obj)).To(Succeed()) // Should have config error because certificate doesn't exist yet - cond := testutils.FindCondition(obj.Status.Conditions, ConditionReady) + cond := testutils.FindCondition(obj.Status.Conditions, string(ngrokv1alpha1.AgentEndpointConditionReady)) g.Expect(cond).NotTo(BeNil()) g.Expect(cond.Status).To(Equal(metav1.ConditionFalse)) - g.Expect(cond.Reason).To(Equal(ReasonConfigError)) + g.Expect(cond.Reason).To(Equal(string(ngrokv1alpha1.AgentEndpointReasonConfigError))) }, timeout, interval).Should(Succeed()) By("Creating the client certificate secret") @@ -1112,10 +1112,10 @@ cCzFoVcb6XWg4MpPeZ25v+xA g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(agentEndpoint), obj)).To(Succeed()) // Should now be ready because certificate exists - cond := testutils.FindCondition(obj.Status.Conditions, ConditionReady) + cond := testutils.FindCondition(obj.Status.Conditions, string(ngrokv1alpha1.AgentEndpointConditionReady)) g.Expect(cond).NotTo(BeNil()) g.Expect(cond.Status).To(Equal(metav1.ConditionTrue)) - g.Expect(cond.Reason).To(Equal(ReasonEndpointActive)) + g.Expect(cond.Reason).To(Equal(string(ngrokv1alpha1.AgentEndpointReasonActive))) // Verify status was updated g.Expect(obj.Status.AssignedURL).To(Equal("tcp://cert-watch.tcp.ngrok.io:12345")) diff --git a/internal/controller/bindings/boundendpoint_conditions.go b/internal/controller/bindings/boundendpoint_conditions.go index 0cfd78c8..3fdc342b 100644 --- a/internal/controller/bindings/boundendpoint_conditions.go +++ b/internal/controller/bindings/boundendpoint_conditions.go @@ -8,39 +8,17 @@ import ( "github.com/ngrok/ngrok-operator/internal/ngrokapi" ) -const ( - // Condition types for BoundEndpoint - ConditionTypeReady = "Ready" - ConditionTypeServicesCreated = "ServicesCreated" - ConditionTypeConnectivityVerified = "ConnectivityVerified" -) - -const ( - // Reasons for Ready condition - ReasonBoundEndpointReady = "BoundEndpointReady" - ReasonServicesNotCreated = "ServicesNotCreated" - ReasonConnectivityNotVerified = "ConnectivityNotVerified" - - // Reasons for ServicesCreated condition - ReasonServicesCreated = "ServicesCreated" - ReasonServiceCreationFailed = "ServiceCreationFailed" - - // Reasons for ConnectivityVerified condition - ReasonConnectivityVerified = "ConnectivityVerified" - ReasonConnectivityFailed = "ConnectivityFailed" -) - // setServicesCreatedCondition sets the ServicesCreated condition -func setServicesCreatedCondition(be *bindingsv1alpha1.BoundEndpoint, created bool, reason, message string) { +func setServicesCreatedCondition(be *bindingsv1alpha1.BoundEndpoint, created bool, reason bindingsv1alpha1.BoundEndpointConditionServicesCreatedReason, message string) { status := metav1.ConditionTrue if !created { status = metav1.ConditionFalse } condition := metav1.Condition{ - Type: ConditionTypeServicesCreated, + Type: string(bindingsv1alpha1.BoundEndpointConditionServicesCreated), Status: status, - Reason: reason, + Reason: string(reason), Message: message, ObservedGeneration: be.Generation, } @@ -51,19 +29,19 @@ func setServicesCreatedCondition(be *bindingsv1alpha1.BoundEndpoint, created boo // setConnectivityVerifiedCondition sets the ConnectivityVerified condition func setConnectivityVerifiedCondition(be *bindingsv1alpha1.BoundEndpoint, verified bool, err error) { status := metav1.ConditionTrue - reason := ReasonConnectivityVerified + reason := bindingsv1alpha1.BoundEndpointReasonConnectivityVerified message := "Successfully connected to upstream service" if !verified { status = metav1.ConditionFalse - reason = ReasonConnectivityFailed + reason = bindingsv1alpha1.BoundEndpointReasonConnectivityFailed message = ngrokapi.SanitizeErrorMessage(err.Error()) } condition := metav1.Condition{ - Type: ConditionTypeConnectivityVerified, + Type: string(bindingsv1alpha1.BoundEndpointConditionConnectivityVerified), Status: status, - Reason: reason, + Reason: string(reason), Message: message, ObservedGeneration: be.Generation, } @@ -74,36 +52,37 @@ func setConnectivityVerifiedCondition(be *bindingsv1alpha1.BoundEndpoint, verifi // calculateReadyCondition calculates the overall Ready condition based on other conditions func calculateReadyCondition(be *bindingsv1alpha1.BoundEndpoint) { // Check if services were created - servicesCreatedCondition := meta.FindStatusCondition(be.Status.Conditions, ConditionTypeServicesCreated) + servicesCreatedCondition := meta.FindStatusCondition(be.Status.Conditions, string(bindingsv1alpha1.BoundEndpointConditionServicesCreated)) servicesCreated := servicesCreatedCondition != nil && servicesCreatedCondition.Status == metav1.ConditionTrue // Check if connectivity was verified - connectivityCondition := meta.FindStatusCondition(be.Status.Conditions, ConditionTypeConnectivityVerified) + connectivityCondition := meta.FindStatusCondition(be.Status.Conditions, string(bindingsv1alpha1.BoundEndpointConditionConnectivityVerified)) connectivityVerified := connectivityCondition != nil && connectivityCondition.Status == metav1.ConditionTrue // Overall ready status ready := servicesCreated && connectivityVerified // Determine reason and message based on state - var reason, message string + var reason bindingsv1alpha1.BoundEndpointConditionReadyReason + var message string switch { case ready: - reason = ReasonBoundEndpointReady + reason = bindingsv1alpha1.BoundEndpointReasonReady message = "BoundEndpoint is ready" case !servicesCreated: if servicesCreatedCondition != nil { - reason = servicesCreatedCondition.Reason + reason = bindingsv1alpha1.BoundEndpointConditionReadyReason(servicesCreatedCondition.Reason) message = servicesCreatedCondition.Message } else { - reason = ReasonServicesNotCreated + reason = bindingsv1alpha1.BoundEndpointReasonServicesNotCreated message = "Services not yet created" } case !connectivityVerified: if connectivityCondition != nil { - reason = connectivityCondition.Reason + reason = bindingsv1alpha1.BoundEndpointConditionReadyReason(connectivityCondition.Reason) message = connectivityCondition.Message } else { - reason = ReasonConnectivityNotVerified + reason = bindingsv1alpha1.BoundEndpointReasonConnectivityNotVerified message = "Connectivity not yet verified" } default: @@ -115,16 +94,16 @@ func calculateReadyCondition(be *bindingsv1alpha1.BoundEndpoint) { } // setReadyCondition sets the Ready condition -func setReadyCondition(be *bindingsv1alpha1.BoundEndpoint, ready bool, reason, message string) { +func setReadyCondition(be *bindingsv1alpha1.BoundEndpoint, ready bool, reason bindingsv1alpha1.BoundEndpointConditionReadyReason, message string) { status := metav1.ConditionTrue if !ready { status = metav1.ConditionFalse } condition := metav1.Condition{ - Type: ConditionTypeReady, + Type: string(bindingsv1alpha1.BoundEndpointConditionReady), Status: status, - Reason: reason, + Reason: string(reason), Message: message, ObservedGeneration: be.Generation, } diff --git a/internal/controller/bindings/boundendpoint_conditions_test.go b/internal/controller/bindings/boundendpoint_conditions_test.go index 7c6f1d65..5d2d5982 100644 --- a/internal/controller/bindings/boundendpoint_conditions_test.go +++ b/internal/controller/bindings/boundendpoint_conditions_test.go @@ -5,8 +5,8 @@ import ( "testing" bindingsv1alpha1 "github.com/ngrok/ngrok-operator/api/bindings/v1alpha1" + "github.com/ngrok/ngrok-operator/internal/controller/conditions" "github.com/stretchr/testify/assert" - "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -32,12 +32,12 @@ func createTestBoundEndpointWithConditions(name, namespace string, conditions [] func TestSetServicesCreatedCondition_Success(t *testing.T) { be := createTestBoundEndpoint("test-be", "ngrok-op") - setServicesCreatedCondition(be, true, ReasonServicesCreated, "Both services created successfully") + setServicesCreatedCondition(be, true, bindingsv1alpha1.BoundEndpointReasonServicesCreated, "Both services created successfully") - cond := meta.FindStatusCondition(be.Status.Conditions, ConditionTypeServicesCreated) + cond := conditions.FindCondition(be.Status.Conditions, bindingsv1alpha1.BoundEndpointConditionServicesCreated) assert.NotNil(t, cond) assert.Equal(t, metav1.ConditionTrue, cond.Status) - assert.Equal(t, ReasonServicesCreated, cond.Reason) + assert.Equal(t, string(bindingsv1alpha1.BoundEndpointReasonServicesCreated), cond.Reason) assert.Equal(t, "Both services created successfully", cond.Message) assert.Equal(t, int64(1), cond.ObservedGeneration) } @@ -45,12 +45,12 @@ func TestSetServicesCreatedCondition_Success(t *testing.T) { func TestSetServicesCreatedCondition_Failure(t *testing.T) { be := createTestBoundEndpoint("test-be", "ngrok-op") - setServicesCreatedCondition(be, false, ReasonServiceCreationFailed, "namespaces \"missing\" not found") + setServicesCreatedCondition(be, false, bindingsv1alpha1.BoundEndpointReasonServiceCreationFailed, "namespaces \"missing\" not found") - cond := meta.FindStatusCondition(be.Status.Conditions, ConditionTypeServicesCreated) + cond := conditions.FindCondition(be.Status.Conditions, bindingsv1alpha1.BoundEndpointConditionServicesCreated) assert.NotNil(t, cond) assert.Equal(t, metav1.ConditionFalse, cond.Status) - assert.Equal(t, ReasonServiceCreationFailed, cond.Reason) + assert.Equal(t, string(bindingsv1alpha1.BoundEndpointReasonServiceCreationFailed), cond.Reason) assert.Equal(t, "namespaces \"missing\" not found", cond.Message) assert.Equal(t, int64(1), cond.ObservedGeneration) } @@ -60,10 +60,10 @@ func TestSetConnectivityVerifiedCondition_Success(t *testing.T) { setConnectivityVerifiedCondition(be, true, nil) - cond := meta.FindStatusCondition(be.Status.Conditions, ConditionTypeConnectivityVerified) + cond := conditions.FindCondition(be.Status.Conditions, bindingsv1alpha1.BoundEndpointConditionConnectivityVerified) assert.NotNil(t, cond) assert.Equal(t, metav1.ConditionTrue, cond.Status) - assert.Equal(t, ReasonConnectivityVerified, cond.Reason) + assert.Equal(t, string(bindingsv1alpha1.BoundEndpointReasonConnectivityVerified), cond.Reason) assert.Equal(t, "Successfully connected to upstream service", cond.Message) assert.Equal(t, int64(1), cond.ObservedGeneration) } @@ -74,10 +74,10 @@ func TestSetConnectivityVerifiedCondition_Failure(t *testing.T) { err := errors.New("dial tcp: lookup my-service.namespace: no such host") setConnectivityVerifiedCondition(be, false, err) - cond := meta.FindStatusCondition(be.Status.Conditions, ConditionTypeConnectivityVerified) + cond := conditions.FindCondition(be.Status.Conditions, bindingsv1alpha1.BoundEndpointConditionConnectivityVerified) assert.NotNil(t, cond) assert.Equal(t, metav1.ConditionFalse, cond.Status) - assert.Equal(t, ReasonConnectivityFailed, cond.Reason) + assert.Equal(t, string(bindingsv1alpha1.BoundEndpointReasonConnectivityFailed), cond.Reason) assert.Contains(t, cond.Message, "dial tcp") assert.Equal(t, int64(1), cond.ObservedGeneration) } @@ -85,47 +85,47 @@ func TestSetConnectivityVerifiedCondition_Failure(t *testing.T) { func TestCalculateReadyCondition_AllReady(t *testing.T) { be := createTestBoundEndpointWithConditions("test-be", "ngrok-op", []metav1.Condition{ { - Type: ConditionTypeServicesCreated, + Type: string(bindingsv1alpha1.BoundEndpointConditionServicesCreated), Status: metav1.ConditionTrue, - Reason: ReasonServicesCreated, + Reason: string(bindingsv1alpha1.BoundEndpointReasonServicesCreated), }, { - Type: ConditionTypeConnectivityVerified, + Type: string(bindingsv1alpha1.BoundEndpointConditionConnectivityVerified), Status: metav1.ConditionTrue, - Reason: ReasonConnectivityVerified, + Reason: string(bindingsv1alpha1.BoundEndpointReasonConnectivityVerified), }, }) calculateReadyCondition(be) - readyCond := meta.FindStatusCondition(be.Status.Conditions, ConditionTypeReady) + readyCond := conditions.FindCondition(be.Status.Conditions, bindingsv1alpha1.BoundEndpointConditionReady) assert.NotNil(t, readyCond) assert.Equal(t, metav1.ConditionTrue, readyCond.Status) - assert.Equal(t, ReasonBoundEndpointReady, readyCond.Reason) + assert.Equal(t, string(bindingsv1alpha1.BoundEndpointReasonReady), readyCond.Reason) assert.Equal(t, "BoundEndpoint is ready", readyCond.Message) } func TestCalculateReadyCondition_ServicesNotCreated(t *testing.T) { be := createTestBoundEndpointWithConditions("test-be", "ngrok-op", []metav1.Condition{ { - Type: ConditionTypeServicesCreated, + Type: string(bindingsv1alpha1.BoundEndpointConditionServicesCreated), Status: metav1.ConditionFalse, - Reason: ReasonServiceCreationFailed, + Reason: string(bindingsv1alpha1.BoundEndpointReasonServiceCreationFailed), Message: "Failed to create target service: namespaces \"missing\" not found", }, { - Type: ConditionTypeConnectivityVerified, + Type: string(bindingsv1alpha1.BoundEndpointConditionConnectivityVerified), Status: metav1.ConditionTrue, - Reason: ReasonConnectivityVerified, + Reason: string(bindingsv1alpha1.BoundEndpointReasonConnectivityVerified), }, }) calculateReadyCondition(be) - readyCond := meta.FindStatusCondition(be.Status.Conditions, ConditionTypeReady) + readyCond := conditions.FindCondition(be.Status.Conditions, bindingsv1alpha1.BoundEndpointConditionReady) assert.NotNil(t, readyCond) assert.Equal(t, metav1.ConditionFalse, readyCond.Status) - assert.Equal(t, ReasonServiceCreationFailed, readyCond.Reason) + assert.Equal(t, string(bindingsv1alpha1.BoundEndpointReasonServiceCreationFailed), readyCond.Reason) assert.Equal(t, "Failed to create target service: namespaces \"missing\" not found", readyCond.Message) } @@ -133,61 +133,61 @@ func TestCalculateReadyCondition_ServicesNotCreatedMissingCondition(t *testing.T be := createTestBoundEndpointWithConditions("test-be", "ngrok-op", []metav1.Condition{ // No ServicesCreated condition { - Type: ConditionTypeConnectivityVerified, + Type: string(bindingsv1alpha1.BoundEndpointConditionConnectivityVerified), Status: metav1.ConditionTrue, - Reason: ReasonConnectivityVerified, + Reason: string(bindingsv1alpha1.BoundEndpointReasonConnectivityVerified), }, }) calculateReadyCondition(be) - readyCond := meta.FindStatusCondition(be.Status.Conditions, ConditionTypeReady) + readyCond := conditions.FindCondition(be.Status.Conditions, bindingsv1alpha1.BoundEndpointConditionReady) assert.NotNil(t, readyCond) assert.Equal(t, metav1.ConditionFalse, readyCond.Status) - assert.Equal(t, ReasonServicesNotCreated, readyCond.Reason) + assert.Equal(t, string(bindingsv1alpha1.BoundEndpointReasonServicesNotCreated), readyCond.Reason) assert.Equal(t, "Services not yet created", readyCond.Message) } func TestCalculateReadyCondition_ConnectivityNotVerified(t *testing.T) { be := createTestBoundEndpointWithConditions("test-be", "ngrok-op", []metav1.Condition{ { - Type: ConditionTypeServicesCreated, + Type: string(bindingsv1alpha1.BoundEndpointConditionServicesCreated), Status: metav1.ConditionTrue, - Reason: ReasonServicesCreated, + Reason: string(bindingsv1alpha1.BoundEndpointReasonServicesCreated), }, { - Type: ConditionTypeConnectivityVerified, + Type: string(bindingsv1alpha1.BoundEndpointConditionConnectivityVerified), Status: metav1.ConditionFalse, - Reason: ReasonConnectivityFailed, + Reason: string(bindingsv1alpha1.BoundEndpointReasonConnectivityFailed), Message: "dial tcp: lookup my-service.namespace: no such host", }, }) calculateReadyCondition(be) - readyCond := meta.FindStatusCondition(be.Status.Conditions, ConditionTypeReady) + readyCond := conditions.FindCondition(be.Status.Conditions, bindingsv1alpha1.BoundEndpointConditionReady) assert.NotNil(t, readyCond) assert.Equal(t, metav1.ConditionFalse, readyCond.Status) - assert.Equal(t, ReasonConnectivityFailed, readyCond.Reason) + assert.Equal(t, string(bindingsv1alpha1.BoundEndpointReasonConnectivityFailed), readyCond.Reason) assert.Equal(t, "dial tcp: lookup my-service.namespace: no such host", readyCond.Message) } func TestCalculateReadyCondition_ConnectivityNotVerifiedMissingCondition(t *testing.T) { be := createTestBoundEndpointWithConditions("test-be", "ngrok-op", []metav1.Condition{ { - Type: ConditionTypeServicesCreated, + Type: string(bindingsv1alpha1.BoundEndpointConditionServicesCreated), Status: metav1.ConditionTrue, - Reason: ReasonServicesCreated, + Reason: string(bindingsv1alpha1.BoundEndpointReasonServicesCreated), }, // No ConnectivityVerified condition }) calculateReadyCondition(be) - readyCond := meta.FindStatusCondition(be.Status.Conditions, ConditionTypeReady) + readyCond := conditions.FindCondition(be.Status.Conditions, bindingsv1alpha1.BoundEndpointConditionReady) assert.NotNil(t, readyCond) assert.Equal(t, metav1.ConditionFalse, readyCond.Status) - assert.Equal(t, ReasonConnectivityNotVerified, readyCond.Reason) + assert.Equal(t, string(bindingsv1alpha1.BoundEndpointReasonConnectivityNotVerified), readyCond.Reason) assert.Equal(t, "Connectivity not yet verified", readyCond.Message) } @@ -196,47 +196,47 @@ func TestCalculateReadyCondition_NoConditions(t *testing.T) { calculateReadyCondition(be) - readyCond := meta.FindStatusCondition(be.Status.Conditions, ConditionTypeReady) + readyCond := conditions.FindCondition(be.Status.Conditions, bindingsv1alpha1.BoundEndpointConditionReady) assert.NotNil(t, readyCond) assert.Equal(t, metav1.ConditionFalse, readyCond.Status) - assert.Equal(t, ReasonServicesNotCreated, readyCond.Reason) + assert.Equal(t, string(bindingsv1alpha1.BoundEndpointReasonServicesNotCreated), readyCond.Reason) assert.Equal(t, "Services not yet created", readyCond.Message) } func TestCalculateReadyCondition_ServicesCreatedTakesPrecedence(t *testing.T) { be := createTestBoundEndpointWithConditions("test-be", "ngrok-op", []metav1.Condition{ { - Type: ConditionTypeServicesCreated, + Type: string(bindingsv1alpha1.BoundEndpointConditionServicesCreated), Status: metav1.ConditionFalse, - Reason: ReasonServiceCreationFailed, + Reason: string(bindingsv1alpha1.BoundEndpointReasonServiceCreationFailed), Message: "Target namespace not found", }, { - Type: ConditionTypeConnectivityVerified, + Type: string(bindingsv1alpha1.BoundEndpointConditionConnectivityVerified), Status: metav1.ConditionFalse, - Reason: ReasonConnectivityFailed, + Reason: string(bindingsv1alpha1.BoundEndpointReasonConnectivityFailed), Message: "Connection timeout", }, }) calculateReadyCondition(be) - readyCond := meta.FindStatusCondition(be.Status.Conditions, ConditionTypeReady) + readyCond := conditions.FindCondition(be.Status.Conditions, bindingsv1alpha1.BoundEndpointConditionReady) assert.NotNil(t, readyCond) assert.Equal(t, metav1.ConditionFalse, readyCond.Status) - assert.Equal(t, ReasonServiceCreationFailed, readyCond.Reason) + assert.Equal(t, string(bindingsv1alpha1.BoundEndpointReasonServiceCreationFailed), readyCond.Reason) assert.Equal(t, "Target namespace not found", readyCond.Message) } func TestSetReadyCondition(t *testing.T) { be := createTestBoundEndpoint("test-be", "ngrok-op") - setReadyCondition(be, true, ReasonBoundEndpointReady, "BoundEndpoint is ready") + setReadyCondition(be, true, bindingsv1alpha1.BoundEndpointReasonReady, "BoundEndpoint is ready") - readyCond := meta.FindStatusCondition(be.Status.Conditions, ConditionTypeReady) + readyCond := conditions.FindCondition(be.Status.Conditions, bindingsv1alpha1.BoundEndpointConditionReady) assert.NotNil(t, readyCond) assert.Equal(t, metav1.ConditionTrue, readyCond.Status) - assert.Equal(t, ReasonBoundEndpointReady, readyCond.Reason) + assert.Equal(t, string(bindingsv1alpha1.BoundEndpointReasonReady), readyCond.Reason) assert.Equal(t, "BoundEndpoint is ready", readyCond.Message) assert.Equal(t, int64(1), readyCond.ObservedGeneration) } @@ -244,12 +244,12 @@ func TestSetReadyCondition(t *testing.T) { func TestSetReadyCondition_False(t *testing.T) { be := createTestBoundEndpoint("test-be", "ngrok-op") - setReadyCondition(be, false, ReasonServicesNotCreated, "Services not yet created") + setReadyCondition(be, false, bindingsv1alpha1.BoundEndpointReasonServicesNotCreated, "Services not yet created") - readyCond := meta.FindStatusCondition(be.Status.Conditions, ConditionTypeReady) + readyCond := conditions.FindCondition(be.Status.Conditions, bindingsv1alpha1.BoundEndpointConditionReady) assert.NotNil(t, readyCond) assert.Equal(t, metav1.ConditionFalse, readyCond.Status) - assert.Equal(t, ReasonServicesNotCreated, readyCond.Reason) + assert.Equal(t, string(bindingsv1alpha1.BoundEndpointReasonServicesNotCreated), readyCond.Reason) assert.Equal(t, "Services not yet created", readyCond.Message) assert.Equal(t, int64(1), readyCond.ObservedGeneration) } diff --git a/internal/controller/bindings/boundendpoint_controller.go b/internal/controller/bindings/boundendpoint_controller.go index 72204fb3..a9cb42a8 100644 --- a/internal/controller/bindings/boundendpoint_controller.go +++ b/internal/controller/bindings/boundendpoint_controller.go @@ -189,7 +189,7 @@ func (r *BoundEndpointReconciler) create(ctx context.Context, cr *bindingsv1alph } // Both services created successfully - setServicesCreatedCondition(cr, true, ReasonServicesCreated, "Target and Upstream services created") + setServicesCreatedCondition(cr, true, bindingsv1alpha1.BoundEndpointReasonServicesCreated, "Target and Upstream services created") updateServiceRefs(cr, targetService, upstreamService) // Test connectivity @@ -210,7 +210,7 @@ func (r *BoundEndpointReconciler) createTargetService(ctx context.Context, owner log.Error(err, "Failed to create Target Service") ngrokErr := ngrokapi.NewNgrokError(err, ngrokapi.NgrokOpErrFailedToCreateTargetService, "Failed to create Target Service") - setServicesCreatedCondition(owner, false, ReasonServiceCreationFailed, ngrokErr.Error()) + setServicesCreatedCondition(owner, false, bindingsv1alpha1.BoundEndpointReasonServiceCreationFailed, ngrokErr.Error()) return ngrokErr } @@ -229,7 +229,7 @@ func (r *BoundEndpointReconciler) createUpstreamService(ctx context.Context, own log.Error(err, "Failed to create Upstream Service") ngrokErr := ngrokapi.NewNgrokError(err, ngrokapi.NgrokOpErrFailedToCreateUpstreamService, "Failed to create Upstream Service") - setServicesCreatedCondition(owner, false, ReasonServiceCreationFailed, ngrokErr.Error()) + setServicesCreatedCondition(owner, false, bindingsv1alpha1.BoundEndpointReasonServiceCreationFailed, ngrokErr.Error()) return ngrokErr } @@ -310,7 +310,7 @@ func (r *BoundEndpointReconciler) update(ctx context.Context, cr *bindingsv1alph } // Both services exist and are up to date - setServicesCreatedCondition(cr, true, ReasonServicesCreated, "Target and Upstream services created") + setServicesCreatedCondition(cr, true, bindingsv1alpha1.BoundEndpointReasonServicesCreated, "Target and Upstream services created") updateServiceRefs(cr, desiredTargetService, desiredUpstreamService) // Test connectivity diff --git a/internal/controller/bindings/boundendpoint_integration_test.go b/internal/controller/bindings/boundendpoint_integration_test.go index 20e721d0..5b98040a 100644 --- a/internal/controller/bindings/boundendpoint_integration_test.go +++ b/internal/controller/bindings/boundendpoint_integration_test.go @@ -106,7 +106,7 @@ var _ = Describe("BoundEndpoint Controller", func() { g.Expect(err).NotTo(HaveOccurred()) // Check ServicesCreated condition - servicesCreatedCond := testutils.FindCondition(be.Status.Conditions, ConditionTypeServicesCreated) + servicesCreatedCond := testutils.FindCondition(be.Status.Conditions, string(bindingsv1alpha1.BoundEndpointConditionServicesCreated)) g.Expect(servicesCreatedCond).NotTo(BeNil(), "ServicesCreated condition should exist") g.Expect(servicesCreatedCond.Status).To(Equal(metav1.ConditionTrue), "ServicesCreated should be True") @@ -121,7 +121,7 @@ var _ = Describe("BoundEndpoint Controller", func() { // NOTE: Ready condition will be False in test env because connectivity check fails // (no actual service to dial). We just verify the condition exists and services were created. - readyCond := testutils.FindCondition(be.Status.Conditions, ConditionTypeReady) + readyCond := testutils.FindCondition(be.Status.Conditions, string(bindingsv1alpha1.BoundEndpointConditionReady)) g.Expect(readyCond).NotTo(BeNil(), "Ready condition should exist") }, timeout, interval).Should(Succeed()) @@ -219,7 +219,7 @@ var _ = Describe("BoundEndpoint Controller", func() { be := list.Items[0] // Check ServicesCreated condition - servicesCreatedCond := testutils.FindCondition(be.Status.Conditions, ConditionTypeServicesCreated) + servicesCreatedCond := testutils.FindCondition(be.Status.Conditions, string(bindingsv1alpha1.BoundEndpointConditionServicesCreated)) g.Expect(servicesCreatedCond).NotTo(BeNil()) g.Expect(servicesCreatedCond.Status).To(Equal(metav1.ConditionTrue)) }, timeout, interval).Should(Succeed()) @@ -268,7 +268,7 @@ var _ = Describe("BoundEndpoint Controller", func() { be := list.Items[0] boundEndpointName = be.Name - servicesCreatedCond := testutils.FindCondition(be.Status.Conditions, ConditionTypeServicesCreated) + servicesCreatedCond := testutils.FindCondition(be.Status.Conditions, string(bindingsv1alpha1.BoundEndpointConditionServicesCreated)) g.Expect(servicesCreatedCond).NotTo(BeNil()) g.Expect(servicesCreatedCond.Status).To(Equal(metav1.ConditionTrue)) }, timeout, interval).Should(Succeed()) @@ -309,7 +309,7 @@ var _ = Describe("BoundEndpoint Controller", func() { g.Expect(be.Status.EndpointsSummary).To(Equal("2 endpoints")) // KEY TEST: ServicesCreated condition should remain True - servicesCreatedCond := testutils.FindCondition(be.Status.Conditions, ConditionTypeServicesCreated) + servicesCreatedCond := testutils.FindCondition(be.Status.Conditions, string(bindingsv1alpha1.BoundEndpointConditionServicesCreated)) g.Expect(servicesCreatedCond).NotTo(BeNil()) g.Expect(servicesCreatedCond.Status).To(Equal(metav1.ConditionTrue), "ServicesCreated should stay True after adding endpoint") @@ -357,14 +357,14 @@ var _ = Describe("BoundEndpoint Controller", func() { }, be) g.Expect(err).NotTo(HaveOccurred()) - servicesCreatedCond := testutils.FindCondition(be.Status.Conditions, ConditionTypeServicesCreated) + servicesCreatedCond := testutils.FindCondition(be.Status.Conditions, string(bindingsv1alpha1.BoundEndpointConditionServicesCreated)) g.Expect(servicesCreatedCond).NotTo(BeNil()) g.Expect(servicesCreatedCond.Status).To(Equal(metav1.ConditionFalse)) - g.Expect(servicesCreatedCond.Reason).To(Equal(ReasonServiceCreationFailed)) + g.Expect(servicesCreatedCond.Reason).To(Equal(string(bindingsv1alpha1.BoundEndpointReasonServiceCreationFailed))) g.Expect(servicesCreatedCond.Message).To(ContainSubstring("namespace")) // Ready should also be False - readyCond := testutils.FindCondition(be.Status.Conditions, ConditionTypeReady) + readyCond := testutils.FindCondition(be.Status.Conditions, string(bindingsv1alpha1.BoundEndpointConditionReady)) g.Expect(readyCond).NotTo(BeNil()) g.Expect(readyCond.Status).To(Equal(metav1.ConditionFalse)) }, timeout, interval).Should(Succeed()) diff --git a/internal/controller/conditions/helpers.go b/internal/controller/conditions/helpers.go new file mode 100644 index 00000000..78c27e08 --- /dev/null +++ b/internal/controller/conditions/helpers.go @@ -0,0 +1,20 @@ +package conditions + +import ( + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// FindCondition is a generic helper that finds a condition in a list of conditions. +// It accepts typed condition types (e.g., DomainConditionType, IPPolicyConditionType) +// and eliminates the need for explicit string() casting at call sites. +// +// Example usage: +// +// condition := FindCondition(domain.Status.Conditions, ingressv1alpha1.DomainConditionReady) +// if condition != nil && condition.Status == metav1.ConditionTrue { +// // Domain is ready +// } +func FindCondition[T ~string](conditions []metav1.Condition, condType T) *metav1.Condition { + return meta.FindStatusCondition(conditions, string(condType)) +} diff --git a/internal/controller/conditions/helpers_test.go b/internal/controller/conditions/helpers_test.go new file mode 100644 index 00000000..a856319e --- /dev/null +++ b/internal/controller/conditions/helpers_test.go @@ -0,0 +1,80 @@ +package conditions + +import ( + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// TestConditionType is a test type for condition types +type TestConditionType string + +const ( + TestConditionReady TestConditionType = "Ready" + TestConditionInProgress TestConditionType = "InProgress" +) + +func TestFindCondition(t *testing.T) { + conditions := []metav1.Condition{ + { + Type: "Ready", + Status: metav1.ConditionTrue, + Reason: "AllGood", + }, + { + Type: "InProgress", + Status: metav1.ConditionFalse, + Reason: "Waiting", + }, + } + + tests := []struct { + name string + condType TestConditionType + wantFound bool + wantType string + }{ + { + name: "find existing condition", + condType: TestConditionReady, + wantFound: true, + wantType: "Ready", + }, + { + name: "find another existing condition", + condType: TestConditionInProgress, + wantFound: true, + wantType: "InProgress", + }, + { + name: "condition not found", + condType: TestConditionType("NotExists"), + wantFound: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := FindCondition(conditions, tt.condType) + if tt.wantFound { + if result == nil { + t.Errorf("FindCondition() = nil, want non-nil") + return + } + if result.Type != tt.wantType { + t.Errorf("FindCondition() Type = %v, want %v", result.Type, tt.wantType) + } + } else if result != nil { + t.Errorf("FindCondition() = %v, want nil", result) + } + }) + } +} + +func TestFindCondition_EmptyList(t *testing.T) { + var conditions []metav1.Condition + result := FindCondition(conditions, TestConditionReady) + if result != nil { + t.Errorf("FindCondition() on empty list = %v, want nil", result) + } +} diff --git a/internal/controller/ingress/domain_conditions.go b/internal/controller/ingress/domain_conditions.go index 3cf84b80..155786a2 100644 --- a/internal/controller/ingress/domain_conditions.go +++ b/internal/controller/ingress/domain_conditions.go @@ -11,42 +11,17 @@ import ( "github.com/ngrok/ngrok-operator/internal/ngrokapi" ) -const ( - // condition types for Domain - ConditionDomainReady = "Ready" - ConditionDomainCreated = "DomainCreated" - ConditionCertificateReady = "CertificateReady" - ConditionDNSConfigured = "DNSConfigured" - ConditionProgressing = "Progressing" - - // condition reasons for Domain - ReasonDomainActive = "DomainActive" - ReasonDomainCreated = "DomainCreated" - ReasonDomainInvalid = "DomainInvalid" - ReasonCertificateProvisioning = "CertificateProvisioning" - ReasonCertificateReady = "CertificateReady" - ReasonDNSError = "DNSError" - ReasonACMEChallengeRequired = "ACMEChallengeRequired" - ReasonNgrokManaged = "NgrokManaged" - ReasonProvisioningError = "ProvisioningError" - ReasonWaitingForCertificate = "WaitingForCertificate" - ReasonDanglingDNSRecord = "DanglingDNSRecord" - ReasonProtectedDomain = "ProtectedDomain" - ReasonDomainCreationFailed = "DomainCreationFailed" - ReasonProvisioning = "Provisioning" -) - // setReadyCondition sets the Ready condition based on the overall domain state -func setDomainReadyCondition(domain *ingressv1alpha1.Domain, ready bool, reason, message string) { +func setDomainReadyCondition(domain *ingressv1alpha1.Domain, ready bool, reason ingressv1alpha1.DomainConditionReadyReason, message string) { status := metav1.ConditionTrue if !ready { status = metav1.ConditionFalse } condition := metav1.Condition{ - Type: ConditionDomainReady, + Type: string(ingressv1alpha1.DomainConditionReady), Status: status, - Reason: reason, + Reason: string(reason), Message: message, ObservedGeneration: domain.Generation, } @@ -55,16 +30,16 @@ func setDomainReadyCondition(domain *ingressv1alpha1.Domain, ready bool, reason, } // setProgressingCondition sets the Progressing condition -func setProgressingCondition(domain *ingressv1alpha1.Domain, progressing bool, reason, message string) { +func setProgressingCondition(domain *ingressv1alpha1.Domain, progressing bool, reason ingressv1alpha1.DomainConditionProgressingReason, message string) { status := metav1.ConditionTrue if !progressing { status = metav1.ConditionFalse } condition := metav1.Condition{ - Type: ConditionProgressing, + Type: string(ingressv1alpha1.DomainConditionProgressing), Status: status, - Reason: reason, + Reason: string(reason), Message: message, ObservedGeneration: domain.Generation, } @@ -72,16 +47,16 @@ func setProgressingCondition(domain *ingressv1alpha1.Domain, progressing bool, r } // setDomainCreatedCondition sets the DomainCreated condition -func setDomainCreatedCondition(domain *ingressv1alpha1.Domain, created bool, reason, message string) { +func setDomainCreatedCondition(domain *ingressv1alpha1.Domain, created bool, reason ingressv1alpha1.DomainConditionCreatedReason, message string) { status := metav1.ConditionTrue if !created { status = metav1.ConditionFalse } condition := metav1.Condition{ - Type: ConditionDomainCreated, + Type: string(ingressv1alpha1.DomainConditionCreated), Status: status, - Reason: reason, + Reason: string(reason), Message: message, ObservedGeneration: domain.Generation, } @@ -90,16 +65,16 @@ func setDomainCreatedCondition(domain *ingressv1alpha1.Domain, created bool, rea } // setCertificateReadyCondition sets the CertificateReady condition -func setCertificateReadyCondition(domain *ingressv1alpha1.Domain, ready bool, reason, message string) { +func setCertificateReadyCondition(domain *ingressv1alpha1.Domain, ready bool, reason ingressv1alpha1.DomainConditionCertificateReadyReason, message string) { status := metav1.ConditionTrue if !ready { status = metav1.ConditionFalse } condition := metav1.Condition{ - Type: ConditionCertificateReady, + Type: string(ingressv1alpha1.DomainConditionCertificateReady), Status: status, - Reason: reason, + Reason: string(reason), Message: message, ObservedGeneration: domain.Generation, } @@ -108,16 +83,16 @@ func setCertificateReadyCondition(domain *ingressv1alpha1.Domain, ready bool, re } // setDNSConfiguredCondition sets the DNSConfigured condition -func setDNSConfiguredCondition(domain *ingressv1alpha1.Domain, configured bool, reason, message string) { +func setDNSConfiguredCondition(domain *ingressv1alpha1.Domain, configured bool, reason ingressv1alpha1.DomainConditionDNSConfiguredReason, message string) { status := metav1.ConditionTrue if !configured { status = metav1.ConditionFalse } condition := metav1.Condition{ - Type: ConditionDNSConfigured, + Type: string(ingressv1alpha1.DomainConditionDNSConfigured), Status: status, - Reason: reason, + Reason: string(reason), Message: message, ObservedGeneration: domain.Generation, } @@ -130,38 +105,38 @@ func updateDomainConditions(domain *ingressv1alpha1.Domain, ngrokDomain *ngrok.R // Handle creation errors first if createErr != nil { message := ngrokapi.SanitizeErrorMessage(createErr.Error()) - setDomainCreatedCondition(domain, false, ReasonDomainCreationFailed, message) - setCertificateReadyCondition(domain, false, ReasonDomainCreationFailed, "Domain creation failed") - setDNSConfiguredCondition(domain, false, ReasonDomainCreationFailed, "Domain creation failed") - setDomainReadyCondition(domain, false, ReasonDomainCreationFailed, message) + setDomainCreatedCondition(domain, false, ingressv1alpha1.DomainCreatedReasonCreationFailed, message) + setCertificateReadyCondition(domain, false, ingressv1alpha1.DomainCertificateReadyReasonCreationFailed, "Domain creation failed") + setDNSConfiguredCondition(domain, false, ingressv1alpha1.DomainDNSConfiguredReasonCreationFailed, "Domain creation failed") + setDomainReadyCondition(domain, false, ingressv1alpha1.DomainReasonCreationFailed, message) return } if domain.Status.ID == "" { message := "Domain could not be reserved" - setDomainCreatedCondition(domain, false, ReasonDomainInvalid, message) - setCertificateReadyCondition(domain, false, ReasonDomainInvalid, message) - setDNSConfiguredCondition(domain, false, ReasonDomainInvalid, message) - setDomainReadyCondition(domain, false, ReasonDomainInvalid, message) + setDomainCreatedCondition(domain, false, ingressv1alpha1.DomainCreatedReasonInvalid, message) + setCertificateReadyCondition(domain, false, ingressv1alpha1.DomainCertificateReadyReasonInvalid, message) + setDNSConfiguredCondition(domain, false, ingressv1alpha1.DomainDNSConfiguredReasonInvalid, message) + setDomainReadyCondition(domain, false, ingressv1alpha1.DomainReasonInvalid, message) return } - setDomainCreatedCondition(domain, true, ReasonDomainCreated, "Domain successfully reserved") + setDomainCreatedCondition(domain, true, ingressv1alpha1.DomainCreatedReasonCreated, "Domain successfully reserved") // Check if its an ngrok domain. If so the DNS and certs are managed by ngrok // and already setup so the domain is ready. if isNgrokManagedDomain(ngrokDomain) { - setCertificateReadyCondition(domain, true, ReasonNgrokManaged, "Certificate managed by ngrok") - setDNSConfiguredCondition(domain, true, ReasonNgrokManaged, "DNS managed by ngrok") - setDomainReadyCondition(domain, true, ReasonDomainActive, "Domain ready for use") + setCertificateReadyCondition(domain, true, ingressv1alpha1.DomainCertificateReadyReasonNgrokManaged, "Certificate managed by ngrok") + setDNSConfiguredCondition(domain, true, ingressv1alpha1.DomainDNSConfiguredReasonNgrokManaged, "DNS managed by ngrok") + setDomainReadyCondition(domain, true, ingressv1alpha1.DomainReasonActive, "Domain ready for use") return } // If the certificate is not null, then the certificate is provisioned and the domain is ready. if domain.Status.Certificate != nil { - setCertificateReadyCondition(domain, true, ReasonCertificateReady, "Certificate provisioned successfully") - setDNSConfiguredCondition(domain, true, ReasonDomainCreated, "DNS records configured") - setDomainReadyCondition(domain, true, ReasonDomainActive, "Domain ready for use") + setCertificateReadyCondition(domain, true, ingressv1alpha1.DomainCertificateReadyReasonReady, "Certificate provisioned successfully") + setDNSConfiguredCondition(domain, true, ingressv1alpha1.DomainDNSConfiguredReasonConfigured, "DNS records configured") + setDomainReadyCondition(domain, true, ingressv1alpha1.DomainReasonActive, "Domain ready for use") return } @@ -188,10 +163,10 @@ func updateDomainConditions(domain *ingressv1alpha1.Domain, ngrokDomain *ngrok.R } } - setCertificateReadyCondition(domain, false, ReasonProvisioningError, message) - setDNSConfiguredCondition(domain, false, ReasonProvisioningError, message) - setDomainReadyCondition(domain, false, ReasonProvisioningError, message) - setProgressingCondition(domain, true, ReasonProvisioning, message) + setCertificateReadyCondition(domain, false, ingressv1alpha1.DomainCertificateReadyReasonProvisioningError, message) + setDNSConfiguredCondition(domain, false, ingressv1alpha1.DomainDNSConfiguredReasonProvisioningError, message) + setDomainReadyCondition(domain, false, ingressv1alpha1.DomainReasonProvisioningError, message) + setProgressingCondition(domain, true, ingressv1alpha1.DomainReasonProvisioning, message) } // Helper functions to determine domain and certificate status @@ -221,7 +196,7 @@ func IsDomainReady(domain *ingressv1alpha1.Domain) bool { } // Then check the Ready condition for more detailed status - readyCondition := meta.FindStatusCondition(domain.Status.Conditions, ConditionDomainReady) + readyCondition := meta.FindStatusCondition(domain.Status.Conditions, string(ingressv1alpha1.DomainConditionReady)) if readyCondition == nil { // No ready condition set yet, so it's not ready return false diff --git a/internal/controller/ingress/domain_conditions_test.go b/internal/controller/ingress/domain_conditions_test.go index e36b6bc5..bb154066 100644 --- a/internal/controller/ingress/domain_conditions_test.go +++ b/internal/controller/ingress/domain_conditions_test.go @@ -7,10 +7,10 @@ import ( "github.com/ngrok/ngrok-api-go/v7" "github.com/stretchr/testify/assert" - "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ingressv1alpha1 "github.com/ngrok/ngrok-operator/api/ingress/v1alpha1" + "github.com/ngrok/ngrok-operator/internal/controller/conditions" ) // Helper function to create a test domain @@ -54,25 +54,25 @@ func TestUpdateDomainConditions_CreationError(t *testing.T) { updateDomainConditions(domain, nil, createErr) // All conditions should be false with creation failed reason - readyCondition := meta.FindStatusCondition(domain.Status.Conditions, ConditionDomainReady) + readyCondition := conditions.FindCondition(domain.Status.Conditions, ingressv1alpha1.DomainConditionReady) assert.NotNil(t, readyCondition) assert.Equal(t, metav1.ConditionFalse, readyCondition.Status) - assert.Equal(t, ReasonDomainCreationFailed, readyCondition.Reason) + assert.Equal(t, string(ingressv1alpha1.DomainReasonCreationFailed), readyCondition.Reason) - createdCondition := meta.FindStatusCondition(domain.Status.Conditions, ConditionDomainCreated) + createdCondition := conditions.FindCondition(domain.Status.Conditions, ingressv1alpha1.DomainConditionCreated) assert.NotNil(t, createdCondition) assert.Equal(t, metav1.ConditionFalse, createdCondition.Status) - assert.Equal(t, ReasonDomainCreationFailed, createdCondition.Reason) + assert.Equal(t, string(ingressv1alpha1.DomainCreatedReasonCreationFailed), createdCondition.Reason) - certCondition := meta.FindStatusCondition(domain.Status.Conditions, ConditionCertificateReady) + certCondition := conditions.FindCondition(domain.Status.Conditions, ingressv1alpha1.DomainConditionCertificateReady) assert.NotNil(t, certCondition) assert.Equal(t, metav1.ConditionFalse, certCondition.Status) - assert.Equal(t, ReasonDomainCreationFailed, certCondition.Reason) + assert.Equal(t, string(ingressv1alpha1.DomainCertificateReadyReasonCreationFailed), certCondition.Reason) - dnsCondition := meta.FindStatusCondition(domain.Status.Conditions, ConditionDNSConfigured) + dnsCondition := conditions.FindCondition(domain.Status.Conditions, ingressv1alpha1.DomainConditionDNSConfigured) assert.NotNil(t, dnsCondition) assert.Equal(t, metav1.ConditionFalse, dnsCondition.Status) - assert.Equal(t, ReasonDomainCreationFailed, dnsCondition.Reason) + assert.Equal(t, string(ingressv1alpha1.DomainDNSConfiguredReasonCreationFailed), dnsCondition.Reason) } func TestUpdateDomainConditions_NoID(t *testing.T) { @@ -81,10 +81,10 @@ func TestUpdateDomainConditions_NoID(t *testing.T) { updateDomainConditions(domain, nil, nil) // All conditions should be false with invalid reason - readyCondition := meta.FindStatusCondition(domain.Status.Conditions, ConditionDomainReady) + readyCondition := conditions.FindCondition(domain.Status.Conditions, ingressv1alpha1.DomainConditionReady) assert.NotNil(t, readyCondition) assert.Equal(t, metav1.ConditionFalse, readyCondition.Status) - assert.Equal(t, ReasonDomainInvalid, readyCondition.Reason) + assert.Equal(t, string(ingressv1alpha1.DomainReasonInvalid), readyCondition.Reason) } func TestUpdateDomainConditions_NgrokManagedDomain(t *testing.T) { @@ -98,25 +98,25 @@ func TestUpdateDomainConditions_NgrokManagedDomain(t *testing.T) { updateDomainConditions(domain, ngrokDomain, nil) // All conditions should be true for ngrok managed domains - readyCondition := meta.FindStatusCondition(domain.Status.Conditions, ConditionDomainReady) + readyCondition := conditions.FindCondition(domain.Status.Conditions, ingressv1alpha1.DomainConditionReady) assert.NotNil(t, readyCondition) assert.Equal(t, metav1.ConditionTrue, readyCondition.Status) - assert.Equal(t, ReasonDomainActive, readyCondition.Reason) + assert.Equal(t, string(ingressv1alpha1.DomainReasonActive), readyCondition.Reason) - createdCondition := meta.FindStatusCondition(domain.Status.Conditions, ConditionDomainCreated) + createdCondition := conditions.FindCondition(domain.Status.Conditions, ingressv1alpha1.DomainConditionCreated) assert.NotNil(t, createdCondition) assert.Equal(t, metav1.ConditionTrue, createdCondition.Status) - assert.Equal(t, ReasonDomainCreated, createdCondition.Reason) + assert.Equal(t, string(ingressv1alpha1.DomainCreatedReasonCreated), createdCondition.Reason) - certCondition := meta.FindStatusCondition(domain.Status.Conditions, ConditionCertificateReady) + certCondition := conditions.FindCondition(domain.Status.Conditions, ingressv1alpha1.DomainConditionCertificateReady) assert.NotNil(t, certCondition) assert.Equal(t, metav1.ConditionTrue, certCondition.Status) - assert.Equal(t, ReasonNgrokManaged, certCondition.Reason) + assert.Equal(t, string(ingressv1alpha1.DomainCertificateReadyReasonNgrokManaged), certCondition.Reason) - dnsCondition := meta.FindStatusCondition(domain.Status.Conditions, ConditionDNSConfigured) + dnsCondition := conditions.FindCondition(domain.Status.Conditions, ingressv1alpha1.DomainConditionDNSConfigured) assert.NotNil(t, dnsCondition) assert.Equal(t, metav1.ConditionTrue, dnsCondition.Status) - assert.Equal(t, ReasonNgrokManaged, dnsCondition.Reason) + assert.Equal(t, string(ingressv1alpha1.DomainDNSConfiguredReasonNgrokManaged), dnsCondition.Reason) } func TestUpdateDomainConditions_CustomDomainWithCertificate(t *testing.T) { @@ -130,20 +130,20 @@ func TestUpdateDomainConditions_CustomDomainWithCertificate(t *testing.T) { updateDomainConditions(domain, ngrokDomain, nil) // All conditions should be true when certificate is provisioned - readyCondition := meta.FindStatusCondition(domain.Status.Conditions, ConditionDomainReady) + readyCondition := conditions.FindCondition(domain.Status.Conditions, ingressv1alpha1.DomainConditionReady) assert.NotNil(t, readyCondition) assert.Equal(t, metav1.ConditionTrue, readyCondition.Status) - assert.Equal(t, ReasonDomainActive, readyCondition.Reason) + assert.Equal(t, string(ingressv1alpha1.DomainReasonActive), readyCondition.Reason) - certCondition := meta.FindStatusCondition(domain.Status.Conditions, ConditionCertificateReady) + certCondition := conditions.FindCondition(domain.Status.Conditions, ingressv1alpha1.DomainConditionCertificateReady) assert.NotNil(t, certCondition) assert.Equal(t, metav1.ConditionTrue, certCondition.Status) - assert.Equal(t, ReasonCertificateReady, certCondition.Reason) + assert.Equal(t, string(ingressv1alpha1.DomainCertificateReadyReasonReady), certCondition.Reason) - dnsCondition := meta.FindStatusCondition(domain.Status.Conditions, ConditionDNSConfigured) + dnsCondition := conditions.FindCondition(domain.Status.Conditions, ingressv1alpha1.DomainConditionDNSConfigured) assert.NotNil(t, dnsCondition) assert.Equal(t, metav1.ConditionTrue, dnsCondition.Status) - assert.Equal(t, ReasonDomainCreated, dnsCondition.Reason) + assert.Equal(t, string(ingressv1alpha1.DomainDNSConfiguredReasonConfigured), dnsCondition.Reason) } func TestUpdateDomainConditions_CustomDomainProvisioning(t *testing.T) { @@ -157,22 +157,22 @@ func TestUpdateDomainConditions_CustomDomainProvisioning(t *testing.T) { updateDomainConditions(domain, ngrokDomain, nil) // Domain should be created but not ready - createdCondition := meta.FindStatusCondition(domain.Status.Conditions, ConditionDomainCreated) + createdCondition := conditions.FindCondition(domain.Status.Conditions, ingressv1alpha1.DomainConditionCreated) assert.NotNil(t, createdCondition) assert.Equal(t, metav1.ConditionTrue, createdCondition.Status) - assert.Equal(t, ReasonDomainCreated, createdCondition.Reason) + assert.Equal(t, string(ingressv1alpha1.DomainCreatedReasonCreated), createdCondition.Reason) // But not ready due to provisioning - readyCondition := meta.FindStatusCondition(domain.Status.Conditions, ConditionDomainReady) + readyCondition := conditions.FindCondition(domain.Status.Conditions, ingressv1alpha1.DomainConditionReady) assert.NotNil(t, readyCondition) assert.Equal(t, metav1.ConditionFalse, readyCondition.Status) - assert.Equal(t, ReasonProvisioningError, readyCondition.Reason) + assert.Equal(t, string(ingressv1alpha1.DomainReasonProvisioningError), readyCondition.Reason) // Progressing should be true - progressingCondition := meta.FindStatusCondition(domain.Status.Conditions, ConditionProgressing) + progressingCondition := conditions.FindCondition(domain.Status.Conditions, ingressv1alpha1.DomainConditionProgressing) assert.NotNil(t, progressingCondition) assert.Equal(t, metav1.ConditionTrue, progressingCondition.Status) - assert.Equal(t, ReasonProvisioning, progressingCondition.Reason) + assert.Equal(t, string(ingressv1alpha1.DomainReasonProvisioning), progressingCondition.Reason) } func TestUpdateDomainConditions_CustomDomainWithProvisioningJob(t *testing.T) { @@ -194,7 +194,7 @@ func TestUpdateDomainConditions_CustomDomainWithProvisioningJob(t *testing.T) { updateDomainConditions(domain, ngrokDomain, nil) // Should include job details in message - readyCondition := meta.FindStatusCondition(domain.Status.Conditions, ConditionDomainReady) + readyCondition := conditions.FindCondition(domain.Status.Conditions, ingressv1alpha1.DomainConditionReady) assert.NotNil(t, readyCondition) assert.Contains(t, readyCondition.Message, "DNS_ERROR") assert.Contains(t, readyCondition.Message, "DNS records not configured") @@ -312,9 +312,9 @@ func TestIsDomainReady(t *testing.T) { ID: "rd_123", Conditions: []metav1.Condition{ { - Type: ConditionDomainReady, + Type: string(ingressv1alpha1.DomainConditionReady), Status: metav1.ConditionFalse, - Reason: ReasonProvisioningError, + Reason: string(ingressv1alpha1.DomainReasonProvisioningError), }, }, }, @@ -328,9 +328,9 @@ func TestIsDomainReady(t *testing.T) { ID: "rd_123", Conditions: []metav1.Condition{ { - Type: ConditionDomainReady, + Type: string(ingressv1alpha1.DomainConditionReady), Status: metav1.ConditionTrue, - Reason: ReasonDomainActive, + Reason: string(ingressv1alpha1.DomainReasonActive), }, }, }, diff --git a/internal/controller/ingress/domain_controller_test.go b/internal/controller/ingress/domain_controller_test.go index d97e8ac6..fc0d2a54 100644 --- a/internal/controller/ingress/domain_controller_test.go +++ b/internal/controller/ingress/domain_controller_test.go @@ -9,10 +9,10 @@ import ( "github.com/ngrok/ngrok-api-go/v7" ingressv1alpha1 "github.com/ngrok/ngrok-operator/api/ingress/v1alpha1" "github.com/ngrok/ngrok-operator/internal/controller" + "github.com/ngrok/ngrok-operator/internal/controller/conditions" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/rand" "sigs.k8s.io/controller-runtime/pkg/client" @@ -200,16 +200,16 @@ var _ = Describe("DomainReconciler", func() { g.Expect(err).ToNot(HaveOccurred()) // Should have success conditions set - readyCondition := meta.FindStatusCondition(foundDomain.Status.Conditions, ConditionDomainReady) + readyCondition := conditions.FindCondition(foundDomain.Status.Conditions, ingressv1alpha1.DomainConditionReady) g.Expect(readyCondition).ToNot(BeNil()) g.Expect(readyCondition.Status).To(Equal(metav1.ConditionTrue)) - g.Expect(readyCondition.Reason).To(Equal(ReasonDomainActive)) + g.Expect(readyCondition.Reason).To(Equal(string(ingressv1alpha1.DomainReasonActive))) // Should have domain created condition - createdCondition := meta.FindStatusCondition(foundDomain.Status.Conditions, ConditionDomainCreated) + createdCondition := conditions.FindCondition(foundDomain.Status.Conditions, ingressv1alpha1.DomainConditionCreated) g.Expect(createdCondition).ToNot(BeNil()) g.Expect(createdCondition.Status).To(Equal(metav1.ConditionTrue)) - g.Expect(createdCondition.Reason).To(Equal(ReasonDomainCreated)) + g.Expect(createdCondition.Reason).To(Equal(string(ingressv1alpha1.DomainCreatedReasonCreated))) // Domain should have been created in ngrok g.Expect(foundDomain.Status.ID).ToNot(BeEmpty()) @@ -246,10 +246,10 @@ var _ = Describe("DomainReconciler", func() { g.Expect(err).ToNot(HaveOccurred()) // Should have error conditions set - readyCondition := meta.FindStatusCondition(foundDomain.Status.Conditions, ConditionDomainReady) + readyCondition := conditions.FindCondition(foundDomain.Status.Conditions, ingressv1alpha1.DomainConditionReady) g.Expect(readyCondition).ToNot(BeNil()) g.Expect(readyCondition.Status).To(Equal(metav1.ConditionFalse)) - g.Expect(readyCondition.Reason).To(Equal(ReasonDomainCreationFailed)) + g.Expect(readyCondition.Reason).To(Equal(string(ingressv1alpha1.DomainReasonCreationFailed))) // Domain should not have been created in ngrok g.Expect(foundDomain.Status.ID).To(BeEmpty()) @@ -389,10 +389,10 @@ var _ = Describe("DomainReconciler", func() { g.Expect(err).ToNot(HaveOccurred()) // Should have error conditions set - readyCondition := meta.FindStatusCondition(d.Status.Conditions, ConditionDomainReady) + readyCondition := conditions.FindCondition(d.Status.Conditions, ingressv1alpha1.DomainConditionReady) g.Expect(readyCondition).ToNot(BeNil()) g.Expect(readyCondition.Status).To(Equal(metav1.ConditionFalse)) - g.Expect(readyCondition.Reason).To(Equal(ReasonDomainCreationFailed)) + g.Expect(readyCondition.Reason).To(Equal(string(ingressv1alpha1.DomainReasonCreationFailed))) }, timeout, interval).Should(Succeed()) }) }) @@ -420,7 +420,7 @@ var _ = Describe("DomainReconciler", func() { g.Expect(err).ToNot(HaveOccurred()) // Should still have conditions set (status was updated) - readyCondition := meta.FindStatusCondition(d.Status.Conditions, ConditionDomainReady) + readyCondition := conditions.FindCondition(d.Status.Conditions, ingressv1alpha1.DomainConditionReady) g.Expect(readyCondition).ToNot(BeNil()) // The observed generation should be updated to match the current generation g.Expect(readyCondition.ObservedGeneration).To(Equal(d.Generation)) @@ -611,10 +611,10 @@ var _ = Describe("DomainReconciler", func() { } // Check that we have the main conditions - readyCondition := meta.FindStatusCondition(d.Status.Conditions, "Ready") + readyCondition := conditions.FindCondition(d.Status.Conditions, ingressv1alpha1.DomainConditionReady) g.Expect(readyCondition).ToNot(BeNil()) - domainCreatedCondition := meta.FindStatusCondition(d.Status.Conditions, "DomainCreated") + domainCreatedCondition := conditions.FindCondition(d.Status.Conditions, ingressv1alpha1.DomainConditionCreated) g.Expect(domainCreatedCondition).ToNot(BeNil()) }, timeout, interval).Should(Succeed()) }) @@ -671,12 +671,12 @@ var _ = Describe("DomainReconciler", func() { g.Expect(d.Status.Conditions).ToNot(BeEmpty()) // All conditions should be True for ngrok subdomains - readyCondition := meta.FindStatusCondition(d.Status.Conditions, "Ready") + readyCondition := conditions.FindCondition(d.Status.Conditions, ingressv1alpha1.DomainConditionReady) g.Expect(readyCondition).ToNot(BeNil()) g.Expect(readyCondition.Status).To(Equal(metav1.ConditionTrue)) g.Expect(readyCondition.Reason).To(Equal("DomainActive")) - certCondition := meta.FindStatusCondition(d.Status.Conditions, "CertificateReady") + certCondition := conditions.FindCondition(d.Status.Conditions, ingressv1alpha1.DomainConditionCertificateReady) g.Expect(certCondition).ToNot(BeNil()) g.Expect(certCondition.Status).To(Equal(metav1.ConditionTrue)) g.Expect(certCondition.Reason).To(Equal("NgrokManaged")) diff --git a/internal/controller/ingress/ippolicy_conditions.go b/internal/controller/ingress/ippolicy_conditions.go index e3cf5094..97b4852a 100644 --- a/internal/controller/ingress/ippolicy_conditions.go +++ b/internal/controller/ingress/ippolicy_conditions.go @@ -7,32 +7,17 @@ import ( ingressv1alpha1 "github.com/ngrok/ngrok-operator/api/ingress/v1alpha1" ) -const ( - // condition types for IPPolicy - ConditionIPPolicyReady = "Ready" - ConditionIPPolicyCreated = "IPPolicyCreated" - ConditionIPPolicyRulesConfigured = "RulesConfigured" - - // condition reasons for IPPolicy - ReasonIPPolicyActive = "IPPolicyActive" - ReasonIPPolicyCreated = "IPPolicyCreated" - ReasonIPPolicyRulesConfigured = "IPPolicyRulesConfigured" - ReasonIPPolicyRulesConfigurationError = "IPPolicyRulesConfigurationError" - ReasonIPPolicyInvalidCIDR = "IPPolicyInvalidCIDR" - ReasonIPPolicyCreationFailed = "IPPolicyCreationFailed" -) - // setIPPolicyReadyCondition sets the Ready condition based on the overall IP policy state -func setIPPolicyReadyCondition(ipPolicy *ingressv1alpha1.IPPolicy, ready bool, reason, message string) { +func setIPPolicyReadyCondition(ipPolicy *ingressv1alpha1.IPPolicy, ready bool, reason ingressv1alpha1.IPPolicyConditionReadyReason, message string) { status := metav1.ConditionTrue if !ready { status = metav1.ConditionFalse } condition := metav1.Condition{ - Type: ConditionIPPolicyReady, + Type: string(ingressv1alpha1.IPPolicyConditionReady), Status: status, - Reason: reason, + Reason: string(reason), Message: message, ObservedGeneration: ipPolicy.Generation, } @@ -41,16 +26,16 @@ func setIPPolicyReadyCondition(ipPolicy *ingressv1alpha1.IPPolicy, ready bool, r } // setIPPolicyCreatedCondition sets the IPPolicyCreated condition -func setIPPolicyCreatedCondition(ipPolicy *ingressv1alpha1.IPPolicy, created bool, reason, message string) { +func setIPPolicyCreatedCondition(ipPolicy *ingressv1alpha1.IPPolicy, created bool, reason ingressv1alpha1.IPPolicyConditionCreatedReason, message string) { status := metav1.ConditionTrue if !created { status = metav1.ConditionFalse } condition := metav1.Condition{ - Type: ConditionIPPolicyCreated, + Type: string(ingressv1alpha1.IPPolicyConditionCreated), Status: status, - Reason: reason, + Reason: string(reason), Message: message, ObservedGeneration: ipPolicy.Generation, } @@ -59,16 +44,16 @@ func setIPPolicyCreatedCondition(ipPolicy *ingressv1alpha1.IPPolicy, created boo } // setIPPolicyRulesConfiguredCondition sets the RulesConfigured condition -func setIPPolicyRulesConfiguredCondition(ipPolicy *ingressv1alpha1.IPPolicy, configured bool, reason, message string) { +func setIPPolicyRulesConfiguredCondition(ipPolicy *ingressv1alpha1.IPPolicy, configured bool, reason ingressv1alpha1.IPPolicyConditionRulesConfiguredReason, message string) { status := metav1.ConditionTrue if !configured { status = metav1.ConditionFalse } condition := metav1.Condition{ - Type: ConditionIPPolicyRulesConfigured, + Type: string(ingressv1alpha1.IPPolicyConditionRulesConfigured), Status: status, - Reason: reason, + Reason: string(reason), Message: message, ObservedGeneration: ipPolicy.Generation, } @@ -80,25 +65,25 @@ func setIPPolicyRulesConfiguredCondition(ipPolicy *ingressv1alpha1.IPPolicy, con func calculateIPPolicyReadyCondition(ipPolicy *ingressv1alpha1.IPPolicy) { // check IP Policy created condition ipPolicyCreated := false - createdCondition := meta.FindStatusCondition(ipPolicy.Status.Conditions, ConditionIPPolicyCreated) + createdCondition := meta.FindStatusCondition(ipPolicy.Status.Conditions, string(ingressv1alpha1.IPPolicyConditionCreated)) if createdCondition != nil && createdCondition.Status == metav1.ConditionTrue { ipPolicyCreated = true } // check IP Policy rules configured condition ipPolicyRulesConfigured := false - rulesConfiguredCondition := meta.FindStatusCondition(ipPolicy.Status.Conditions, ConditionIPPolicyRulesConfigured) + rulesConfiguredCondition := meta.FindStatusCondition(ipPolicy.Status.Conditions, string(ingressv1alpha1.IPPolicyConditionRulesConfigured)) if rulesConfiguredCondition != nil && rulesConfiguredCondition.Status == metav1.ConditionTrue { ipPolicyRulesConfigured = true } switch { case ipPolicyCreated && ipPolicyRulesConfigured: - setIPPolicyReadyCondition(ipPolicy, true, ReasonIPPolicyActive, "IP Policy is active") + setIPPolicyReadyCondition(ipPolicy, true, ingressv1alpha1.IPPolicyReasonActive, "IP Policy is active") case ipPolicyCreated && !ipPolicyRulesConfigured: - setIPPolicyReadyCondition(ipPolicy, false, ReasonIPPolicyRulesConfigurationError, "IP Policy rules are not configured") + setIPPolicyReadyCondition(ipPolicy, false, ingressv1alpha1.IPPolicyReasonRulesConfigurationError, "IP Policy rules are not configured") default: - setIPPolicyReadyCondition(ipPolicy, false, ReasonIPPolicyCreationFailed, "IP Policy is not ready") + setIPPolicyReadyCondition(ipPolicy, false, ingressv1alpha1.IPPolicyReasonCreationFailed, "IP Policy is not ready") } } diff --git a/internal/controller/ingress/ippolicy_conditions_test.go b/internal/controller/ingress/ippolicy_conditions_test.go index 503af095..94ae14ed 100644 --- a/internal/controller/ingress/ippolicy_conditions_test.go +++ b/internal/controller/ingress/ippolicy_conditions_test.go @@ -4,10 +4,10 @@ import ( "testing" "github.com/stretchr/testify/assert" - "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ingressv1alpha1 "github.com/ngrok/ngrok-operator/api/ingress/v1alpha1" + "github.com/ngrok/ngrok-operator/internal/controller/conditions" ) // Tests that ipPolicy condition ready is set correctly @@ -19,25 +19,25 @@ func TestSetIPPolicyReadyCondition(t *testing.T) { }, } - setIPPolicyReadyCondition(ipPolicy, true, ReasonIPPolicyActive, "IP Policy is active") + setIPPolicyReadyCondition(ipPolicy, true, ingressv1alpha1.IPPolicyReasonActive, "IP Policy is active") - condition := meta.FindStatusCondition(ipPolicy.Status.Conditions, ConditionIPPolicyReady) + condition := conditions.FindCondition(ipPolicy.Status.Conditions, ingressv1alpha1.IPPolicyConditionReady) if condition == nil { t.Fatal("Expected Ready condition to be set") } assert.Equal(t, metav1.ConditionTrue, condition.Status) - assert.Equal(t, ReasonIPPolicyActive, condition.Reason) + assert.Equal(t, string(ingressv1alpha1.IPPolicyReasonActive), condition.Reason) assert.Equal(t, "IP Policy is active", condition.Message) assert.Equal(t, int64(1), condition.ObservedGeneration) - setIPPolicyReadyCondition(ipPolicy, false, ReasonIPPolicyCreationFailed, "Failed to create IP Policy") + setIPPolicyReadyCondition(ipPolicy, false, ingressv1alpha1.IPPolicyReasonCreationFailed, "Failed to create IP Policy") - condition = meta.FindStatusCondition(ipPolicy.Status.Conditions, ConditionIPPolicyReady) + condition = conditions.FindCondition(ipPolicy.Status.Conditions, ingressv1alpha1.IPPolicyConditionReady) if condition == nil { t.Fatal("Expected Ready condition to be set") } assert.Equal(t, metav1.ConditionFalse, condition.Status) - assert.Equal(t, ReasonIPPolicyCreationFailed, condition.Reason) + assert.Equal(t, string(ingressv1alpha1.IPPolicyReasonCreationFailed), condition.Reason) assert.Equal(t, "Failed to create IP Policy", condition.Message) assert.Equal(t, int64(1), condition.ObservedGeneration) } @@ -51,25 +51,25 @@ func TestSetIPPolicyCreatedCondition(t *testing.T) { }, } - setIPPolicyCreatedCondition(ipPolicy, true, ReasonIPPolicyCreated, "IP Policy has been created") + setIPPolicyCreatedCondition(ipPolicy, true, ingressv1alpha1.IPPolicyCreatedReasonCreated, "IP Policy has been created") - condition := meta.FindStatusCondition(ipPolicy.Status.Conditions, ConditionIPPolicyCreated) + condition := conditions.FindCondition(ipPolicy.Status.Conditions, ingressv1alpha1.IPPolicyConditionCreated) if condition == nil { t.Fatal("Expected Created condition to be set") } assert.Equal(t, metav1.ConditionTrue, condition.Status) - assert.Equal(t, ReasonIPPolicyCreated, condition.Reason) + assert.Equal(t, string(ingressv1alpha1.IPPolicyCreatedReasonCreated), condition.Reason) assert.Equal(t, "IP Policy has been created", condition.Message) assert.Equal(t, int64(1), condition.ObservedGeneration) - setIPPolicyCreatedCondition(ipPolicy, false, ReasonIPPolicyCreationFailed, "Failed to create IP Policy") + setIPPolicyCreatedCondition(ipPolicy, false, ingressv1alpha1.IPPolicyCreatedReasonCreationFailed, "Failed to create IP Policy") - condition = meta.FindStatusCondition(ipPolicy.Status.Conditions, ConditionIPPolicyCreated) + condition = conditions.FindCondition(ipPolicy.Status.Conditions, ingressv1alpha1.IPPolicyConditionCreated) if condition == nil { t.Fatal("Expected Created condition to be set") } assert.Equal(t, metav1.ConditionFalse, condition.Status) - assert.Equal(t, ReasonIPPolicyCreationFailed, condition.Reason) + assert.Equal(t, string(ingressv1alpha1.IPPolicyCreatedReasonCreationFailed), condition.Reason) assert.Equal(t, "Failed to create IP Policy", condition.Message) assert.Equal(t, int64(1), condition.ObservedGeneration) } @@ -83,25 +83,25 @@ func TestSetIPPolicyRulesConfiguredCondition(t *testing.T) { }, } - setIPPolicyRulesConfiguredCondition(ipPolicy, true, ReasonIPPolicyRulesConfigured, "IP Policy rules have been configured") + setIPPolicyRulesConfiguredCondition(ipPolicy, true, ingressv1alpha1.IPPolicyRulesConfiguredReasonConfigured, "IP Policy rules have been configured") - condition := meta.FindStatusCondition(ipPolicy.Status.Conditions, ConditionIPPolicyRulesConfigured) + condition := conditions.FindCondition(ipPolicy.Status.Conditions, ingressv1alpha1.IPPolicyConditionRulesConfigured) if condition == nil { t.Fatal("Expected RulesConfigured condition to be set") } assert.Equal(t, metav1.ConditionTrue, condition.Status) - assert.Equal(t, ReasonIPPolicyRulesConfigured, condition.Reason) + assert.Equal(t, string(ingressv1alpha1.IPPolicyRulesConfiguredReasonConfigured), condition.Reason) assert.Equal(t, "IP Policy rules have been configured", condition.Message) assert.Equal(t, int64(1), condition.ObservedGeneration) - setIPPolicyRulesConfiguredCondition(ipPolicy, false, ReasonIPPolicyRulesConfigurationError, "Failed to configure IP Policy rules") + setIPPolicyRulesConfiguredCondition(ipPolicy, false, ingressv1alpha1.IPPolicyRulesConfiguredReasonConfigurationError, "Failed to configure IP Policy rules") - condition = meta.FindStatusCondition(ipPolicy.Status.Conditions, ConditionIPPolicyRulesConfigured) + condition = conditions.FindCondition(ipPolicy.Status.Conditions, ingressv1alpha1.IPPolicyConditionRulesConfigured) if condition == nil { t.Fatal("Expected RulesConfigured condition to be set") } assert.Equal(t, metav1.ConditionFalse, condition.Status) - assert.Equal(t, ReasonIPPolicyRulesConfigurationError, condition.Reason) + assert.Equal(t, string(ingressv1alpha1.IPPolicyRulesConfiguredReasonConfigurationError), condition.Reason) assert.Equal(t, "Failed to configure IP Policy rules", condition.Message) assert.Equal(t, int64(1), condition.ObservedGeneration) } diff --git a/internal/controller/ingress/ippolicy_controller.go b/internal/controller/ingress/ippolicy_controller.go index 2b175988..28f120a3 100644 --- a/internal/controller/ingress/ippolicy_controller.go +++ b/internal/controller/ingress/ippolicy_controller.go @@ -106,12 +106,12 @@ func (r *IPPolicyReconciler) create(ctx context.Context, policy *ingressv1alpha1 Metadata: policy.Spec.Metadata, }) if err != nil { - setIPPolicyCreatedCondition(policy, false, ReasonIPPolicyCreationFailed, err.Error()) + setIPPolicyCreatedCondition(policy, false, ingressv1alpha1.IPPolicyCreatedReasonCreationFailed, err.Error()) return err } policy.Status.ID = remotePolicy.ID - setIPPolicyCreatedCondition(policy, true, ReasonIPPolicyCreated, "IP Policy successfully created") + setIPPolicyCreatedCondition(policy, true, ingressv1alpha1.IPPolicyCreatedReasonCreated, "IP Policy successfully created") err = r.createOrUpdateIPPolicyRules(ctx, policy) @@ -128,9 +128,9 @@ func (r *IPPolicyReconciler) update(ctx context.Context, policy *ingressv1alpha1 return r.Status().Update(ctx, policy) } - setIPPolicyCreatedCondition(policy, false, ReasonIPPolicyCreationFailed, err.Error()) - setIPPolicyRulesConfiguredCondition(policy, false, ReasonIPPolicyCreationFailed, err.Error()) - setIPPolicyReadyCondition(policy, false, ReasonIPPolicyCreationFailed, err.Error()) + setIPPolicyCreatedCondition(policy, false, ingressv1alpha1.IPPolicyCreatedReasonCreationFailed, err.Error()) + setIPPolicyRulesConfiguredCondition(policy, false, ingressv1alpha1.IPPolicyRulesConfiguredReasonConfigurationError, err.Error()) + setIPPolicyReadyCondition(policy, false, ingressv1alpha1.IPPolicyReasonCreationFailed, err.Error()) return r.controller.ReconcileStatus(ctx, policy, err) } @@ -144,7 +144,7 @@ func (r *IPPolicyReconciler) update(ctx context.Context, policy *ingressv1alpha1 Metadata: ptr.To(policy.Spec.Metadata), }) if err != nil { - setIPPolicyReadyCondition(policy, false, ReasonIPPolicyCreationFailed, err.Error()) + setIPPolicyReadyCondition(policy, false, ingressv1alpha1.IPPolicyReasonCreationFailed, err.Error()) return err } r.Recorder.Event(policy, v1.EventTypeNormal, "Updated", fmt.Sprintf("Updated IPPolicy %s", policy.Name)) @@ -174,7 +174,7 @@ func (r *IPPolicyReconciler) createOrUpdateIPPolicyRules(ctx context.Context, po } if len(remoteRules) == 0 { - setIPPolicyRulesConfiguredCondition(policy, false, ReasonIPPolicyRulesConfigurationError, "No rules configured for IP Policy") + setIPPolicyRulesConfiguredCondition(policy, false, ingressv1alpha1.IPPolicyRulesConfiguredReasonConfigurationError, "No rules configured for IP Policy") } iter := newIPPolicyDiff(policy.Status.ID, remoteRules, policy.Spec.Rules) @@ -193,14 +193,14 @@ func (r *IPPolicyReconciler) createOrUpdateIPPolicyRules(ctx context.Context, po rule, err := r.IPPolicyRulesClient.Create(ctx, c) if err != nil { if ngrok.IsErrorCode(err, ngrokapi.NgrokOpErrInvalidCIDR.Code) { - setIPPolicyRulesConfiguredCondition(policy, false, ReasonIPPolicyInvalidCIDR, err.Error()) - setIPPolicyReadyCondition(policy, false, ReasonIPPolicyInvalidCIDR, err.Error()) + setIPPolicyRulesConfiguredCondition(policy, false, ingressv1alpha1.IPPolicyRulesConfiguredReasonInvalidCIDR, err.Error()) + setIPPolicyReadyCondition(policy, false, ingressv1alpha1.IPPolicyReasonInvalidCIDR, err.Error()) } else { - setIPPolicyRulesConfiguredCondition(policy, false, ReasonIPPolicyRulesConfigurationError, err.Error()) - setIPPolicyReadyCondition(policy, false, ReasonIPPolicyRulesConfigurationError, err.Error()) + setIPPolicyRulesConfiguredCondition(policy, false, ingressv1alpha1.IPPolicyRulesConfiguredReasonConfigurationError, err.Error()) + setIPPolicyReadyCondition(policy, false, ingressv1alpha1.IPPolicyReasonRulesConfigurationError, err.Error()) } } - setIPPolicyRulesConfiguredCondition(policy, true, ReasonIPPolicyRulesConfigured, "All rules configured for IP Policy") + setIPPolicyRulesConfiguredCondition(policy, true, ingressv1alpha1.IPPolicyRulesConfiguredReasonConfigured, "All rules configured for IP Policy") log.V(3).Info("Created IP Policy Rule", "id", rule.ID, "policy.id", policy.Status.ID, "cidr", rule.CIDR, "action", rule.Action) } @@ -209,15 +209,15 @@ func (r *IPPolicyReconciler) createOrUpdateIPPolicyRules(ctx context.Context, po rule, err := r.IPPolicyRulesClient.Update(ctx, u) if err != nil { if ngrok.IsErrorCode(err, ngrokapi.NgrokOpErrInvalidCIDR.Code) { - setIPPolicyRulesConfiguredCondition(policy, false, ReasonIPPolicyInvalidCIDR, err.Error()) - setIPPolicyReadyCondition(policy, false, ReasonIPPolicyInvalidCIDR, err.Error()) + setIPPolicyRulesConfiguredCondition(policy, false, ingressv1alpha1.IPPolicyRulesConfiguredReasonInvalidCIDR, err.Error()) + setIPPolicyReadyCondition(policy, false, ingressv1alpha1.IPPolicyReasonInvalidCIDR, err.Error()) } else { - setIPPolicyRulesConfiguredCondition(policy, false, ReasonIPPolicyRulesConfigurationError, err.Error()) - setIPPolicyReadyCondition(policy, false, ReasonIPPolicyRulesConfigurationError, err.Error()) + setIPPolicyRulesConfiguredCondition(policy, false, ingressv1alpha1.IPPolicyRulesConfiguredReasonConfigurationError, err.Error()) + setIPPolicyReadyCondition(policy, false, ingressv1alpha1.IPPolicyReasonRulesConfigurationError, err.Error()) } return err } - setIPPolicyRulesConfiguredCondition(policy, true, ReasonIPPolicyRulesConfigured, "All rules configured for IP Policy") + setIPPolicyRulesConfiguredCondition(policy, true, ingressv1alpha1.IPPolicyRulesConfiguredReasonConfigured, "All rules configured for IP Policy") log.V(3).Info("Updated IP Policy Rule", "id", rule.ID, "policy.id", policy.Status.ID) } } diff --git a/internal/controller/ngrok/cloudendpoint_conditions.go b/internal/controller/ngrok/cloudendpoint_conditions.go index 2b9f61aa..fc97aed4 100644 --- a/internal/controller/ngrok/cloudendpoint_conditions.go +++ b/internal/controller/ngrok/cloudendpoint_conditions.go @@ -8,28 +8,17 @@ import ( domainpkg "github.com/ngrok/ngrok-operator/internal/domain" ) -const ( - // condition types for CloudEndpoint - ConditionCloudEndpointReady = "Ready" - ConditionCloudEndpointCreated = "CloudEndpointCreated" - - // condition reasons for CloudEndpoint - ReasonCloudEndpointActive = "CloudEndpointActive" - ReasonCloudEndpointCreated = "CloudEndpointCreated" - ReasonCloudEndpointCreationFailed = "CloudEndpointCreationFailed" -) - // setCloudEndpointReadyCondition sets the Ready condition -func setCloudEndpointReadyCondition(clep *ngrokv1alpha1.CloudEndpoint, ready bool, reason, message string) { +func setCloudEndpointReadyCondition(clep *ngrokv1alpha1.CloudEndpoint, ready bool, reason ngrokv1alpha1.CloudEndpointConditionReadyReason, message string) { status := metav1.ConditionTrue if !ready { status = metav1.ConditionFalse } condition := metav1.Condition{ - Type: ConditionCloudEndpointReady, + Type: string(ngrokv1alpha1.CloudEndpointConditionReady), Status: status, - Reason: reason, + Reason: string(reason), Message: message, ObservedGeneration: clep.Generation, } @@ -38,16 +27,16 @@ func setCloudEndpointReadyCondition(clep *ngrokv1alpha1.CloudEndpoint, ready boo } // setCloudEndpointCreatedCondition sets the CloudEndpointCreated condition -func setCloudEndpointCreatedCondition(clep *ngrokv1alpha1.CloudEndpoint, created bool, reason, message string) { +func setCloudEndpointCreatedCondition(clep *ngrokv1alpha1.CloudEndpoint, created bool, reason ngrokv1alpha1.CloudEndpointConditionCreatedReason, message string) { status := metav1.ConditionTrue if !created { status = metav1.ConditionFalse } condition := metav1.Condition{ - Type: ConditionCloudEndpointCreated, + Type: string(ngrokv1alpha1.CloudEndpointConditionCreated), Status: status, - Reason: reason, + Reason: string(reason), Message: message, ObservedGeneration: clep.Generation, } @@ -59,7 +48,7 @@ func setCloudEndpointCreatedCondition(clep *ngrokv1alpha1.CloudEndpoint, created func calculateCloudEndpointReadyCondition(clep *ngrokv1alpha1.CloudEndpoint, domainResult *domainpkg.DomainResult) { // Check CloudEndpoint created condition cloudEndpointCreated := false - createdCondition := meta.FindStatusCondition(clep.Status.Conditions, ConditionCloudEndpointCreated) + createdCondition := meta.FindStatusCondition(clep.Status.Conditions, string(ngrokv1alpha1.CloudEndpointConditionCreated)) if createdCondition != nil && createdCondition.Status == metav1.ConditionTrue { cloudEndpointCreated = true } @@ -71,31 +60,32 @@ func calculateCloudEndpointReadyCondition(clep *ngrokv1alpha1.CloudEndpoint, dom ready := cloudEndpointCreated && domainReady // Determine reason and message based on state - var reason, message string + var reason ngrokv1alpha1.CloudEndpointConditionReadyReason + var message string switch { case ready: - reason = ReasonCloudEndpointActive + reason = ngrokv1alpha1.CloudEndpointReasonActive message = "CloudEndpoint is active and ready" case !domainReady: // Use the domain's Ready condition reason/message for better context if domainResult.ReadyReason != "" { - reason = domainResult.ReadyReason + reason = ngrokv1alpha1.CloudEndpointConditionReadyReason(domainResult.ReadyReason) message = domainResult.ReadyMessage } else { - reason = "DomainNotReady" + reason = ngrokv1alpha1.CloudEndpointReasonDomainNotReady message = "Domain is not ready" } case !cloudEndpointCreated: // If CloudEndpointCreated condition exists and is False, use its reason/message if createdCondition != nil && createdCondition.Status == metav1.ConditionFalse { - reason = createdCondition.Reason + reason = ngrokv1alpha1.CloudEndpointConditionReadyReason(createdCondition.Reason) message = createdCondition.Message } else { - reason = "Pending" + reason = ngrokv1alpha1.CloudEndpointReasonPending message = "Waiting for CloudEndpoint to be ready" } default: - reason = "Unknown" + reason = ngrokv1alpha1.CloudEndpointReasonUnknown message = "CloudEndpoint is not ready" } diff --git a/internal/controller/ngrok/cloudendpoint_conditions_test.go b/internal/controller/ngrok/cloudendpoint_conditions_test.go index dfc4d000..ae06ae2d 100644 --- a/internal/controller/ngrok/cloudendpoint_conditions_test.go +++ b/internal/controller/ngrok/cloudendpoint_conditions_test.go @@ -4,10 +4,10 @@ import ( "testing" "github.com/stretchr/testify/assert" - "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ngrokv1alpha1 "github.com/ngrok/ngrok-operator/api/ngrok/v1alpha1" + "github.com/ngrok/ngrok-operator/internal/controller/conditions" domainpkg "github.com/ngrok/ngrok-operator/internal/domain" ) @@ -53,35 +53,35 @@ func createNotReadyDomainResult(reason, message string) *domainpkg.DomainResult func TestCalculateCloudEndpointReadyCondition_AllReady(t *testing.T) { endpoint := createTestCloudEndpointWithConditions("test-endpoint", "default", []metav1.Condition{ { - Type: ConditionCloudEndpointCreated, + Type: string(ngrokv1alpha1.CloudEndpointConditionCreated), Status: metav1.ConditionTrue, - Reason: ReasonCloudEndpointCreated, + Reason: string(ngrokv1alpha1.CloudEndpointReasonCreated), }, }) domainResult := createReadyDomainResult() calculateCloudEndpointReadyCondition(endpoint, domainResult) - readyCondition := meta.FindStatusCondition(endpoint.Status.Conditions, ConditionCloudEndpointReady) + readyCondition := conditions.FindCondition(endpoint.Status.Conditions, ngrokv1alpha1.CloudEndpointConditionReady) assert.NotNil(t, readyCondition) assert.Equal(t, metav1.ConditionTrue, readyCondition.Status) - assert.Equal(t, ReasonCloudEndpointActive, readyCondition.Reason) + assert.Equal(t, string(ngrokv1alpha1.CloudEndpointReasonActive), readyCondition.Reason) assert.Equal(t, "CloudEndpoint is active and ready", readyCondition.Message) } func TestCalculateCloudEndpointReadyCondition_DomainNotReady(t *testing.T) { endpoint := createTestCloudEndpointWithConditions("test-endpoint", "default", []metav1.Condition{ { - Type: ConditionCloudEndpointCreated, + Type: string(ngrokv1alpha1.CloudEndpointConditionCreated), Status: metav1.ConditionTrue, - Reason: ReasonCloudEndpointCreated, + Reason: string(ngrokv1alpha1.CloudEndpointReasonCreated), }, }) domainResult := createNotReadyDomainResult("ProvisioningError", "Certificate provisioning in progress") calculateCloudEndpointReadyCondition(endpoint, domainResult) - readyCondition := meta.FindStatusCondition(endpoint.Status.Conditions, ConditionCloudEndpointReady) + readyCondition := conditions.FindCondition(endpoint.Status.Conditions, ngrokv1alpha1.CloudEndpointConditionReady) assert.NotNil(t, readyCondition) assert.Equal(t, metav1.ConditionFalse, readyCondition.Status) assert.Equal(t, "ProvisioningError", readyCondition.Reason) @@ -91,9 +91,9 @@ func TestCalculateCloudEndpointReadyCondition_DomainNotReady(t *testing.T) { func TestCalculateCloudEndpointReadyCondition_DomainNotReadyNoReason(t *testing.T) { endpoint := createTestCloudEndpointWithConditions("test-endpoint", "default", []metav1.Condition{ { - Type: ConditionCloudEndpointCreated, + Type: string(ngrokv1alpha1.CloudEndpointConditionCreated), Status: metav1.ConditionTrue, - Reason: ReasonCloudEndpointCreated, + Reason: string(ngrokv1alpha1.CloudEndpointReasonCreated), }, }) domainResult := &domainpkg.DomainResult{ @@ -103,19 +103,19 @@ func TestCalculateCloudEndpointReadyCondition_DomainNotReadyNoReason(t *testing. calculateCloudEndpointReadyCondition(endpoint, domainResult) - readyCondition := meta.FindStatusCondition(endpoint.Status.Conditions, ConditionCloudEndpointReady) + readyCondition := conditions.FindCondition(endpoint.Status.Conditions, ngrokv1alpha1.CloudEndpointConditionReady) assert.NotNil(t, readyCondition) assert.Equal(t, metav1.ConditionFalse, readyCondition.Status) - assert.Equal(t, "DomainNotReady", readyCondition.Reason) + assert.Equal(t, string(ngrokv1alpha1.CloudEndpointReasonDomainNotReady), readyCondition.Reason) assert.Equal(t, "Domain is not ready", readyCondition.Message) } func TestCalculateCloudEndpointReadyCondition_CloudEndpointNotCreated(t *testing.T) { endpoint := createTestCloudEndpointWithConditions("test-endpoint", "default", []metav1.Condition{ { - Type: ConditionCloudEndpointCreated, + Type: string(ngrokv1alpha1.CloudEndpointConditionCreated), Status: metav1.ConditionFalse, - Reason: ReasonCloudEndpointCreationFailed, + Reason: string(ngrokv1alpha1.CloudEndpointReasonCreationFailed), Message: "Failed to create CloudEndpoint", }, }) @@ -123,10 +123,10 @@ func TestCalculateCloudEndpointReadyCondition_CloudEndpointNotCreated(t *testing calculateCloudEndpointReadyCondition(endpoint, domainResult) - readyCondition := meta.FindStatusCondition(endpoint.Status.Conditions, ConditionCloudEndpointReady) + readyCondition := conditions.FindCondition(endpoint.Status.Conditions, ngrokv1alpha1.CloudEndpointConditionReady) assert.NotNil(t, readyCondition) assert.Equal(t, metav1.ConditionFalse, readyCondition.Status) - assert.Equal(t, ReasonCloudEndpointCreationFailed, readyCondition.Reason) + assert.Equal(t, string(ngrokv1alpha1.CloudEndpointReasonCreationFailed), readyCondition.Reason) assert.Equal(t, "Failed to create CloudEndpoint", readyCondition.Message) } @@ -138,10 +138,10 @@ func TestCalculateCloudEndpointReadyCondition_CloudEndpointNotCreatedNoCondition calculateCloudEndpointReadyCondition(endpoint, domainResult) - readyCondition := meta.FindStatusCondition(endpoint.Status.Conditions, ConditionCloudEndpointReady) + readyCondition := conditions.FindCondition(endpoint.Status.Conditions, ngrokv1alpha1.CloudEndpointConditionReady) assert.NotNil(t, readyCondition) assert.Equal(t, metav1.ConditionFalse, readyCondition.Status) - assert.Equal(t, "Pending", readyCondition.Reason) + assert.Equal(t, string(ngrokv1alpha1.CloudEndpointReasonPending), readyCondition.Reason) assert.Equal(t, "Waiting for CloudEndpoint to be ready", readyCondition.Message) } @@ -149,9 +149,9 @@ func TestCalculateCloudEndpointReadyCondition_MultipleIssues(t *testing.T) { // Domain not ready should take precedence over other issues endpoint := createTestCloudEndpointWithConditions("test-endpoint", "default", []metav1.Condition{ { - Type: ConditionCloudEndpointCreated, + Type: string(ngrokv1alpha1.CloudEndpointConditionCreated), Status: metav1.ConditionFalse, - Reason: ReasonCloudEndpointCreationFailed, + Reason: string(ngrokv1alpha1.CloudEndpointReasonCreationFailed), Message: "Failed to create CloudEndpoint", }, }) @@ -159,7 +159,7 @@ func TestCalculateCloudEndpointReadyCondition_MultipleIssues(t *testing.T) { calculateCloudEndpointReadyCondition(endpoint, domainResult) - readyCondition := meta.FindStatusCondition(endpoint.Status.Conditions, ConditionCloudEndpointReady) + readyCondition := conditions.FindCondition(endpoint.Status.Conditions, ngrokv1alpha1.CloudEndpointConditionReady) assert.NotNil(t, readyCondition) assert.Equal(t, metav1.ConditionFalse, readyCondition.Status) assert.Equal(t, "ProvisioningError", readyCondition.Reason) @@ -170,9 +170,9 @@ func TestCalculateCloudEndpointReadyCondition_UnknownState(t *testing.T) { // This should not happen in practice, but test the default case endpoint := createTestCloudEndpointWithConditions("test-endpoint", "default", []metav1.Condition{ { - Type: ConditionCloudEndpointCreated, + Type: string(ngrokv1alpha1.CloudEndpointConditionCreated), Status: metav1.ConditionTrue, - Reason: ReasonCloudEndpointCreated, + Reason: string(ngrokv1alpha1.CloudEndpointReasonCreated), }, }) domainResult := createReadyDomainResult() @@ -181,9 +181,9 @@ func TestCalculateCloudEndpointReadyCondition_UnknownState(t *testing.T) { // This is a bit artificial but tests the default branch calculateCloudEndpointReadyCondition(endpoint, domainResult) - readyCondition := meta.FindStatusCondition(endpoint.Status.Conditions, ConditionCloudEndpointReady) + readyCondition := conditions.FindCondition(endpoint.Status.Conditions, ngrokv1alpha1.CloudEndpointConditionReady) assert.NotNil(t, readyCondition) assert.Equal(t, metav1.ConditionTrue, readyCondition.Status) - assert.Equal(t, ReasonCloudEndpointActive, readyCondition.Reason) + assert.Equal(t, string(ngrokv1alpha1.CloudEndpointReasonActive), readyCondition.Reason) assert.Equal(t, "CloudEndpoint is active and ready", readyCondition.Message) } diff --git a/internal/controller/ngrok/cloudendpoint_controller.go b/internal/controller/ngrok/cloudendpoint_controller.go index 8c4fc511..642f1ac5 100644 --- a/internal/controller/ngrok/cloudendpoint_controller.go +++ b/internal/controller/ngrok/cloudendpoint_controller.go @@ -190,12 +190,12 @@ func (r *CloudEndpointReconciler) create(ctx context.Context, clep *ngrokv1alpha ngrokClep, err := r.NgrokClientset.Endpoints().Create(ctx, createParams) if err != nil { - setCloudEndpointCreatedCondition(clep, false, ReasonCloudEndpointCreationFailed, fmt.Sprintf("Failed to create cloud endpoint: %v", err)) + setCloudEndpointCreatedCondition(clep, false, ngrokv1alpha1.CloudEndpointReasonCreationFailed, fmt.Sprintf("Failed to create cloud endpoint: %v", err)) return r.updateStatus(ctx, clep, nil, domainResult, err) } // Set success condition - setCloudEndpointCreatedCondition(clep, true, ReasonCloudEndpointCreated, "CloudEndpoint created successfully") + setCloudEndpointCreatedCondition(clep, true, ngrokv1alpha1.CloudEndpointReasonCreated, "CloudEndpoint created successfully") return r.updateStatus(ctx, clep, ngrokClep, domainResult, nil) } @@ -232,12 +232,12 @@ func (r *CloudEndpointReconciler) update(ctx context.Context, clep *ngrokv1alpha return r.create(ctx, clep) } if err != nil { - setCloudEndpointCreatedCondition(clep, false, ReasonCloudEndpointCreationFailed, fmt.Sprintf("Failed to update cloud endpoint: %v", err)) + setCloudEndpointCreatedCondition(clep, false, ngrokv1alpha1.CloudEndpointReasonCreationFailed, fmt.Sprintf("Failed to update cloud endpoint: %v", err)) return r.updateStatus(ctx, clep, nil, domainResult, err) } // Set success condition - setCloudEndpointCreatedCondition(clep, true, ReasonCloudEndpointCreated, "CloudEndpoint updated successfully") + setCloudEndpointCreatedCondition(clep, true, ngrokv1alpha1.CloudEndpointReasonCreated, "CloudEndpoint updated successfully") return r.updateStatus(ctx, clep, ngrokClep, domainResult, nil) } diff --git a/internal/controller/ngrok/cloudendpoint_controller_test.go b/internal/controller/ngrok/cloudendpoint_controller_test.go index 1ea16ac2..1042b6df 100644 --- a/internal/controller/ngrok/cloudendpoint_controller_test.go +++ b/internal/controller/ngrok/cloudendpoint_controller_test.go @@ -106,7 +106,7 @@ var _ = Describe("CloudEndpoint Controller", func() { g.Expect(obj.Status.ID).NotTo(BeEmpty()) // Check ready condition - cond := findCloudEndpointCondition(obj.Status.Conditions, ConditionCloudEndpointReady) + cond := findCloudEndpointCondition(obj.Status.Conditions, string(ngrokv1alpha1.CloudEndpointConditionReady)) g.Expect(cond).NotTo(BeNil()) g.Expect(cond.Status).To(Equal(metav1.ConditionTrue)) }, timeout, interval).Should(Succeed()) @@ -242,7 +242,7 @@ var _ = Describe("CloudEndpoint Controller", func() { g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(cloudEndpoint), obj)).To(Succeed()) // Check that error condition is set - cond := findCloudEndpointCondition(obj.Status.Conditions, ConditionCloudEndpointCreated) + cond := findCloudEndpointCondition(obj.Status.Conditions, string(ngrokv1alpha1.CloudEndpointConditionCreated)) g.Expect(cond).NotTo(BeNil()) g.Expect(cond.Status).To(Equal(metav1.ConditionFalse)) }, timeout, interval).Should(Succeed()) @@ -533,7 +533,7 @@ var _ = Describe("CloudEndpoint Controller", func() { g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(cloudEndpoint), obj)).To(Succeed()) // Check that error condition is set - cond := findCloudEndpointCondition(obj.Status.Conditions, ConditionCloudEndpointCreated) + cond := findCloudEndpointCondition(obj.Status.Conditions, string(ngrokv1alpha1.CloudEndpointConditionCreated)) g.Expect(cond).NotTo(BeNil()) g.Expect(cond.Status).To(Equal(metav1.ConditionFalse)) }, timeout, interval).Should(Succeed()) diff --git a/internal/domain/manager.go b/internal/domain/manager.go index e88d9be2..f9e9ac8e 100644 --- a/internal/domain/manager.go +++ b/internal/domain/manager.go @@ -129,7 +129,7 @@ func (m *Manager) checkExistingDomain(endpoint ngrokv1alpha1.EndpointWithDomain, endpoint.SetDomainRef(domainRef) // Get domain's Ready condition to propagate reason/message to endpoint - readyCondition := meta.FindStatusCondition(domainObj.Status.Conditions, ingress.ConditionDomainReady) + readyCondition := meta.FindStatusCondition(domainObj.Status.Conditions, string(ingressv1alpha1.DomainConditionReady)) readyReason := ReasonDomainCreating readyMessage := "Domain is being created" if readyCondition != nil { diff --git a/internal/testutils/ginkgo.go b/internal/testutils/ginkgo.go new file mode 100644 index 00000000..dafc954c --- /dev/null +++ b/internal/testutils/ginkgo.go @@ -0,0 +1,52 @@ +package testutils + +import ( + "context" + + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// ExpectCreateNamespace returns a function that creates a namespace with the given name. +// The function uses Gomega's Expect internally, so it should only be used in Ginkgo tests. +// +// Example usage: +// +// createNs := testutils.ExpectCreateNamespace(k8sClient) +// createNs("test-namespace") +// defer testutils.ExpectDeleteNamespace(k8sClient)("test-namespace") +func ExpectCreateNamespace(k8sClient client.Client) func(string) { + return func(name string) { + ns := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + } + Expect(k8sClient.Create(context.Background(), ns)).To(Succeed()) + } +} + +// ExpectDeleteNamespace returns a function that deletes a namespace with the given name. +// The function uses Gomega's Expect internally and expects the delete to succeed or return NotFound. +// This is useful for cleaning up namespaces in defer statements. +// +// Example usage: +// +// deleteNs := testutils.ExpectDeleteNamespace(k8sClient) +// defer deleteNs("test-namespace") +func ExpectDeleteNamespace(k8sClient client.Client) func(string) { + return func(name string) { + ns := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + } + err := k8sClient.Delete(context.Background(), ns) + if err != nil && !apierrors.IsNotFound(err) { + Expect(err).NotTo(HaveOccurred()) + } + } +}