Skip to content

Commit 8cea4f3

Browse files
committed
OTA-423: Validate route host before creating app
1 parent d1d2135 commit 8cea4f3

4 files changed

Lines changed: 100 additions & 4 deletions

File tree

api/v1/updateservice_types.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ const (
5050

5151
// ConditionRegistryCACertFound reports whether the updateservice registry CA cert had been found
5252
ConditionRegistryCACertFound conditionsv1.ConditionType = "RegistryCACertFound"
53+
54+
ConditionReconcileError conditionsv1.ConditionType = "ReconcileError"
5355
)
5456

5557
// +kubebuilder:object:root=true

controllers/names.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ func nameGraphBuilderService(instance *cv1.UpdateService) string {
4747
}
4848

4949
func namePolicyEngineRoute(instance *cv1.UpdateService) string {
50-
return namePolicyEngineService(instance) + "-route"
50+
return instance.Name + "-route"
5151
}
5252

5353
func nameAdditionalTrustedCA(instance *cv1.UpdateService) string {

controllers/updateservice_controller.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"fmt"
66
"reflect"
7+
"regexp"
78

89
"github.com/go-logr/logr"
910
conditionsv1 "github.com/openshift/custom-resource-status/conditions/v1"
@@ -28,6 +29,13 @@ import (
2829
"github.com/openshift/library-go/pkg/route/routeapihelpers"
2930
)
3031

32+
const dns1123LabelFmt string = "^[a-z]([-a-z0-9]*[a-z0-9])?$"
33+
34+
// DNS1123LabelMaxLength is a label's max length in DNS (RFC 1123)
35+
const DNS1123LabelMaxLength int = 63
36+
37+
var dns1123LabelRegexp = regexp.MustCompile(dns1123LabelFmt)
38+
3139
var log = logf.Log.WithName("controller_updateservice")
3240

3341
// blank assignment to verify that ReconcileUpdateService implements reconcile.Reconciler
@@ -78,6 +86,20 @@ func (r *UpdateServiceReconciler) Reconcile(ctx context.Context, req ctrl.Reques
7886
instanceCopy := instance.DeepCopy()
7987
instanceCopy.Status = cv1.UpdateServiceStatus{}
8088

89+
if err := validateRouteName(instanceCopy, req.Name, req.Namespace); err != nil {
90+
conditionsv1.SetStatusCondition(&instanceCopy.Status.Conditions, conditionsv1.Condition{
91+
Type: cv1.ConditionReconcileError,
92+
Status: corev1.ConditionTrue,
93+
Reason: "Unable to create UpdateService route",
94+
Message: err.Error(),
95+
})
96+
if err := r.Client.Status().Update(ctx, instanceCopy); err != nil {
97+
reqLogger.Error(err, "Failed to update Status")
98+
}
99+
reqLogger.Error(err, "Unable to create UpdateService route")
100+
return ctrl.Result{}, nil
101+
}
102+
81103
// 1. Gather conditions
82104
// Look at the existing cluster resources and communicate to kubeResources
83105
// how it should create resources.
@@ -464,6 +486,28 @@ func (r *UpdateServiceReconciler) ensurePolicyEngineService(ctx context.Context,
464486
return nil
465487
}
466488

489+
func validateRouteName(instance *cv1.UpdateService, name string, namespace string) error {
490+
var errReasons []string
491+
routeName := namePolicyEngineRoute(instance) + "-" + namespace
492+
493+
if len(routeName) > DNS1123LabelMaxLength {
494+
errReasons = append(errReasons,
495+
fmt.Sprintf("cannot exceed RFC 1123 maximum length of %d. Shorten the application name and/or namespace.",
496+
DNS1123LabelMaxLength))
497+
}
498+
if !dns1123LabelRegexp.MatchString(routeName) {
499+
errReasons = append(errReasons,
500+
fmt.Sprintf("has invalid format; must comply with %q.", dns1123LabelFmt))
501+
}
502+
numErrors := len(errReasons)
503+
if numErrors == 0 {
504+
return nil
505+
} else if numErrors == 1 {
506+
return fmt.Errorf(fmt.Sprintf("UpdateService route name %q %s", routeName, errReasons[0]))
507+
}
508+
return fmt.Errorf(fmt.Sprintf("UpdateService route name %q %s Route name %s", routeName, errReasons[0], errReasons[1]))
509+
}
510+
467511
func (r *UpdateServiceReconciler) ensurePolicyEngineRoute(ctx context.Context, reqLogger logr.Logger, instance *cv1.UpdateService, resources *kubeResources) error {
468512
route := resources.policyEngineRoute
469513
// Set UpdateService instance as the owner and controller

controllers/updateservice_controller_test.go

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package controllers
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
67
"os"
78
"testing"
@@ -16,7 +17,7 @@ import (
1617
appsv1 "k8s.io/api/apps/v1"
1718
corev1 "k8s.io/api/core/v1"
1819
policyv1beta1 "k8s.io/api/policy/v1beta1"
19-
"k8s.io/apimachinery/pkg/api/errors"
20+
apierrors "k8s.io/apimachinery/pkg/api/errors"
2021
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2122
"k8s.io/apimachinery/pkg/runtime"
2223
"k8s.io/apimachinery/pkg/types"
@@ -190,7 +191,7 @@ func TestEnsurePullSecret(t *testing.T) {
190191

191192
resources, err := newKubeResources(updateservice, testOperandImage, ps, cm)
192193

193-
if !errors.IsNotFound(err) {
194+
if !apierrors.IsNotFound(err) {
194195
err = r.ensurePullSecret(context.TODO(), log, updateservice, resources)
195196
}
196197
found := &corev1.Secret{}
@@ -480,7 +481,7 @@ func TestEnsurePodDisruptionBudget(t *testing.T) {
480481
updateservice := &cv1.UpdateService{}
481482
err := r.Client.Get(context.TODO(), types.NamespacedName{Name: testName, Namespace: testNamespace}, updateservice)
482483
if err != nil {
483-
if errors.IsNotFound(err) {
484+
if apierrors.IsNotFound(err) {
484485
assert.Error(t, err)
485486
}
486487
assert.Error(t, err)
@@ -517,6 +518,55 @@ func TestEnsurePodDisruptionBudget(t *testing.T) {
517518
}
518519
}
519520

521+
func TestValidateRouteName(t *testing.T) {
522+
tests := []struct {
523+
name string
524+
appName string
525+
namespace string
526+
err error
527+
}{
528+
{
529+
name: "RouteNameValid",
530+
appName: "foo",
531+
namespace: "openshift-update-service",
532+
err: nil,
533+
},
534+
{
535+
name: "RouteNameMaxLen",
536+
appName: "foo",
537+
namespace: "openshift-update-service-0123456789012345678901234567",
538+
err: nil,
539+
},
540+
{
541+
name: "RouteNameTooLong",
542+
appName: "foo",
543+
namespace: "openshift-update-service-01234567890123456789012345678",
544+
err: errors.New("UpdateService route name \"foo-route-openshift-update-service-01234567890123456789012345678\" cannot exceed RFC 1123 maximum length of 63. Shorten the application name and/or namespace."),
545+
},
546+
{
547+
name: "RouteNameInvalidFormat",
548+
appName: "foo",
549+
namespace: "openshift-update-service-012345678901234567890123456.",
550+
err: errors.New(fmt.Sprintf("UpdateService route name \"foo-route-openshift-update-service-012345678901234567890123456.\" has invalid format; must comply with %q.", dns1123LabelFmt)),
551+
},
552+
{
553+
name: "RouteNameMultipleErrors",
554+
appName: "foo",
555+
namespace: "openshift-update-service-0123456789012345678901234567.",
556+
err: errors.New("UpdateService route name \"foo-route-openshift-update-service-0123456789012345678901234567.\" cannot exceed RFC 1123 maximum length of 63. Shorten the application name and/or namespace. " +
557+
fmt.Sprintf("Route name has invalid format; must comply with %q.", dns1123LabelFmt)),
558+
},
559+
}
560+
561+
for _, test := range tests {
562+
t.Run(test.name, func(t *testing.T) {
563+
updateservice := newDefaultUpdateService()
564+
err := validateRouteName(updateservice, test.appName, test.namespace)
565+
assert.Equal(t, test.err, err)
566+
})
567+
}
568+
}
569+
520570
func TestEnsurePolicyEngineRoute(t *testing.T) {
521571
tests := []struct {
522572
name string

0 commit comments

Comments
 (0)