Skip to content

Commit 2af8ddb

Browse files
committed
fix: use internal HTTP URL for operator SDK calls
1 parent e853a59 commit 2af8ddb

2 files changed

Lines changed: 100 additions & 7 deletions

File tree

internal/controller/codercontrolplane_controller.go

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1383,6 +1383,19 @@ func (r *CoderControlPlaneReconciler) desiredStatus(
13831383
return nextStatus
13841384
}
13851385

1386+
func controlPlaneSDKURL(coderControlPlane *coderv1alpha1.CoderControlPlane) string {
1387+
if coderControlPlane == nil {
1388+
return ""
1389+
}
1390+
1391+
servicePort := coderControlPlane.Spec.Service.Port
1392+
if servicePort == 0 {
1393+
servicePort = defaultControlPlanePort
1394+
}
1395+
1396+
return fmt.Sprintf("http://%s.%s.svc.cluster.local:%d", coderControlPlane.Name, coderControlPlane.Namespace, servicePort)
1397+
}
1398+
13861399
func (r *CoderControlPlaneReconciler) reconcileOperatorAccess(
13871400
ctx context.Context,
13881401
coderControlPlane *coderv1alpha1.CoderControlPlane,
@@ -1543,8 +1556,9 @@ func (r *CoderControlPlaneReconciler) reconcileLicense(
15431556
return ctrl.Result{}, nil
15441557
}
15451558

1546-
if strings.TrimSpace(nextStatus.URL) == "" {
1547-
return ctrl.Result{}, fmt.Errorf("assertion failed: control plane URL must not be empty when licenseSecretRef is configured")
1559+
controlPlaneURL := controlPlaneSDKURL(coderControlPlane)
1560+
if strings.TrimSpace(controlPlaneURL) == "" {
1561+
return ctrl.Result{}, fmt.Errorf("assertion failed: control plane SDK URL must not be empty when licenseSecretRef is configured")
15481562
}
15491563

15501564
operatorTokenSecretName := strings.TrimSpace(nextStatus.OperatorTokenSecretRef.Name)
@@ -1644,7 +1658,7 @@ func (r *CoderControlPlaneReconciler) reconcileLicense(
16441658
}
16451659

16461660
if nextStatus.LicenseLastApplied != nil && nextStatus.LicenseLastAppliedHash == licenseHash {
1647-
hasAnyLicense, hasLicenseErr := r.LicenseUploader.HasAnyLicense(ctx, nextStatus.URL, operatorToken)
1661+
hasAnyLicense, hasLicenseErr := r.LicenseUploader.HasAnyLicense(ctx, controlPlaneURL, operatorToken)
16481662
if hasLicenseErr != nil {
16491663
var sdkErr *codersdk.Error
16501664
if errors.As(hasLicenseErr, &sdkErr) {
@@ -1702,7 +1716,7 @@ func (r *CoderControlPlaneReconciler) reconcileLicense(
17021716
}
17031717
}
17041718

1705-
if err := r.LicenseUploader.AddLicense(ctx, nextStatus.URL, operatorToken, licenseJWT); err != nil {
1719+
if err := r.LicenseUploader.AddLicense(ctx, controlPlaneURL, operatorToken, licenseJWT); err != nil {
17061720
if isDuplicateLicenseUploadError(err) {
17071721
now := metav1.Now()
17081722
nextStatus.LicenseLastApplied = &now
@@ -1803,8 +1817,9 @@ func (r *CoderControlPlaneReconciler) reconcileEntitlements(
18031817
nextStatus.OperatorTokenSecretRef == nil {
18041818
return ctrl.Result{}, nil
18051819
}
1806-
if strings.TrimSpace(nextStatus.URL) == "" {
1807-
return ctrl.Result{}, fmt.Errorf("assertion failed: control plane URL must not be empty when querying entitlements")
1820+
controlPlaneURL := controlPlaneSDKURL(coderControlPlane)
1821+
if strings.TrimSpace(controlPlaneURL) == "" {
1822+
return ctrl.Result{}, fmt.Errorf("assertion failed: control plane SDK URL must not be empty when querying entitlements")
18081823
}
18091824
if r.EntitlementsInspector == nil {
18101825
return ctrl.Result{}, nil
@@ -1828,7 +1843,7 @@ func (r *CoderControlPlaneReconciler) reconcileEntitlements(
18281843
return ctrl.Result{RequeueAfter: operatorAccessRetryInterval}, nil
18291844
}
18301845

1831-
entitlements, err := r.EntitlementsInspector.Entitlements(ctx, nextStatus.URL, operatorToken)
1846+
entitlements, err := r.EntitlementsInspector.Entitlements(ctx, controlPlaneURL, operatorToken)
18321847
if err != nil {
18331848
var sdkErr *codersdk.Error
18341849
if errors.As(err, &sdkErr) {

internal/controller/codercontrolplane_controller_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -657,6 +657,84 @@ func TestReconcile_LicenseAppliesOnceAndTracksHash(t *testing.T) {
657657
}
658658
}
659659

660+
func TestReconcile_LicenseUsesInternalHTTPURLWhenTLSEnabled(t *testing.T) {
661+
ensureGatewaySchemeRegistered(t)
662+
ctx := context.Background()
663+
664+
licenseSecret := &corev1.Secret{
665+
ObjectMeta: metav1.ObjectMeta{Name: "test-license-tls-internal-url-secret", Namespace: "default"},
666+
Data: map[string][]byte{
667+
coderv1alpha1.DefaultLicenseSecretKey: []byte("license-jwt-tls-internal-url"),
668+
},
669+
}
670+
if err := k8sClient.Create(ctx, licenseSecret); err != nil {
671+
t.Fatalf("create license secret: %v", err)
672+
}
673+
t.Cleanup(func() {
674+
_ = k8sClient.Delete(ctx, licenseSecret)
675+
})
676+
677+
cp := &coderv1alpha1.CoderControlPlane{
678+
ObjectMeta: metav1.ObjectMeta{Name: "test-license-tls-internal-url", Namespace: "default"},
679+
Spec: coderv1alpha1.CoderControlPlaneSpec{
680+
ExtraEnv: []corev1.EnvVar{{
681+
Name: "CODER_PG_CONNECTION_URL",
682+
Value: "postgres://example/license-tls-internal-url",
683+
}},
684+
TLS: coderv1alpha1.TLSSpec{
685+
SecretNames: []string{"coder-internal-tls-secret"},
686+
},
687+
LicenseSecretRef: &coderv1alpha1.SecretKeySelector{Name: licenseSecret.Name},
688+
},
689+
}
690+
if err := k8sClient.Create(ctx, cp); err != nil {
691+
t.Fatalf("create test CoderControlPlane: %v", err)
692+
}
693+
t.Cleanup(func() {
694+
_ = k8sClient.Delete(ctx, cp)
695+
})
696+
697+
provisioner := &fakeOperatorAccessProvisioner{token: "operator-token-license-tls-url"}
698+
uploader := &fakeLicenseUploader{}
699+
r := &controller.CoderControlPlaneReconciler{
700+
Client: k8sClient,
701+
Scheme: scheme,
702+
OperatorAccessProvisioner: provisioner,
703+
LicenseUploader: uploader,
704+
}
705+
706+
if _, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: types.NamespacedName{Name: cp.Name, Namespace: cp.Namespace}}); err != nil {
707+
t.Fatalf("first reconcile control plane: %v", err)
708+
}
709+
deployment := &appsv1.Deployment{}
710+
if err := k8sClient.Get(ctx, types.NamespacedName{Name: cp.Name, Namespace: cp.Namespace}, deployment); err != nil {
711+
t.Fatalf("get reconciled deployment: %v", err)
712+
}
713+
deployment.Status.ReadyReplicas = 1
714+
deployment.Status.Replicas = 1
715+
if err := k8sClient.Status().Update(ctx, deployment); err != nil {
716+
t.Fatalf("update deployment status: %v", err)
717+
}
718+
719+
if _, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: types.NamespacedName{Name: cp.Name, Namespace: cp.Namespace}}); err != nil {
720+
t.Fatalf("second reconcile control plane: %v", err)
721+
}
722+
if len(uploader.calls) != 1 {
723+
t.Fatalf("expected one license upload call, got %d", len(uploader.calls))
724+
}
725+
if got := uploader.calls[0].coderURL; got != "http://test-license-tls-internal-url.default.svc.cluster.local:80" {
726+
t.Fatalf("expected license upload URL %q, got %q", "http://test-license-tls-internal-url.default.svc.cluster.local:80", got)
727+
}
728+
729+
reconciled := &coderv1alpha1.CoderControlPlane{}
730+
if err := k8sClient.Get(ctx, types.NamespacedName{Name: cp.Name, Namespace: cp.Namespace}, reconciled); err != nil {
731+
t.Fatalf("get reconciled control plane: %v", err)
732+
}
733+
if !strings.HasPrefix(reconciled.Status.URL, "https://") {
734+
t.Fatalf("expected status URL to remain https when TLS is enabled, got %q", reconciled.Status.URL)
735+
}
736+
}
737+
660738
func TestReconcile_LicenseReuploadsWhenBackendHasNoLicenses(t *testing.T) {
661739
ensureGatewaySchemeRegistered(t)
662740
ctx := context.Background()

0 commit comments

Comments
 (0)