diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e495495a2cefb7192270fdf7f198febb5052a974..f1bb5edefdfd44fef170fb39c8e324d9a50c581b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,11 +1,10 @@ -stages: - - test - - build - include: - project: 'paas-tools/infrastructure-ci' file: 'docker-images-ci-templates/DockerImages.gitlab-ci.yml' +stages: + - test + - build Test Operator: stage: test diff --git a/controllers/controller_test.go b/controllers/controller_test.go index 5838158bc2563da05250470ff62b08a0df1cf271..734f82baecf7c17387d12179851945bf827ed5cd 100644 --- a/controllers/controller_test.go +++ b/controllers/controller_test.go @@ -299,6 +299,7 @@ var _ = Describe("GitlabPagesSite controller", func() { } } }) + It("GPS sites in blocked projects are unavailable", func() { gpsSite := newGpsSite(gpsName, gpsNamespace) gpsSite.Spec.Hosts = webservicescernchv1alpha1.SiteUrls{"my-gps-site.web.cern.ch"} @@ -336,9 +337,107 @@ var _ = Describe("GitlabPagesSite controller", func() { Expect(len(routes.Items)).To(Equal(1)) for _, route := range routes.Items { - Expect(route.Spec.Host).To(Equal("my-gps-site.web.cern.ch")) Expect(route.Spec.To.Name).To(Equal(blockedServiceName)) } }) + + It("GPS's Route is assigned to the default router shard", func() { + gpsSite := newGpsSite(gpsName, gpsNamespace) + gpsSite.Spec.Hosts = webservicescernchv1alpha1.SiteUrls{"my-gps-site.web.cern.ch"} + + // create a GPS site as usual + Expect(k8sClient.Create(ctx, gpsSite)).Should(Succeed()) + Eventually(func() bool { + err := k8sClient.Get(ctx, gpsLookupKey, gpsSite) + if err != nil { + return false + } + + return len(gpsSite.Status.Conditions) == 1 && + gpsSite.Status.Conditions[0].Type == webservicescernchv1alpha1.ConditionTypeGitlabPagesSiteCreated && + gpsSite.Status.Conditions[0].Status == metav1.ConditionTrue + }, timeout, interval).Should(BeTrue()) + + route := &routev1.Route{} + routeName := generateRouteName(gpsSite, "my-gps-site.web.cern.ch") + + Expect(k8sClient.Get(ctx, types.NamespacedName{Name: routeName, Namespace: operatorNamespace}, route)).Should(Succeed()) + Expect(route.Labels[routerShardLabel]).To(Equal(defaultRouterShard)) + }) + + It("GPS's Route is assigned to a custom router shard if configured properly", func() { + gpsSite := newGpsSite(gpsName, gpsNamespace) + gpsSite.Spec.Hosts = webservicescernchv1alpha1.SiteUrls{"my-gps-site.web.cern.ch"} + + gpsNs := &corev1.Namespace{} + nsKey := types.NamespacedName{Name: gpsNamespace, Namespace: gpsNamespace} + + Expect(k8sClient.Get(ctx, nsKey, gpsNs)).Should(Succeed()) + + if gpsNs.Annotations == nil { + gpsNs.Annotations = map[string]string{} + } + + gpsNs.Annotations[forceRouterShardNamespaceAnnotation] = "example-router-shard" + + Expect(k8sClient.Update(ctx, gpsNs)).Should(Succeed()) + + // create a GPS site as usual + Expect(k8sClient.Create(ctx, gpsSite)).Should(Succeed()) + Eventually(func() bool { + err := k8sClient.Get(ctx, gpsLookupKey, gpsSite) + if err != nil { + return false + } + + return len(gpsSite.Status.Conditions) == 1 && + gpsSite.Status.Conditions[0].Type == webservicescernchv1alpha1.ConditionTypeGitlabPagesSiteCreated && + gpsSite.Status.Conditions[0].Status == metav1.ConditionTrue + }, timeout, interval).Should(BeTrue()) + + // confirm that the proper shard-related labels/annotations have been applied on the Route + route := &routev1.Route{} + routeName := generateRouteName(gpsSite, "my-gps-site.web.cern.ch") + + Expect(k8sClient.Get(ctx, types.NamespacedName{Name: routeName, Namespace: operatorNamespace}, route)).Should(Succeed()) + Expect(route.Labels[routerShardLabel]).To(Equal("example-router-shard")) + }) + + It("Namespace's IP whitelist is propagated to the Route", func() { + gpsSite := newGpsSite(gpsName, gpsNamespace) + gpsSite.Spec.Hosts = webservicescernchv1alpha1.SiteUrls{"my-gps-site.web.cern.ch"} + + gpsNs := &corev1.Namespace{} + nsKey := types.NamespacedName{Name: gpsNamespace, Namespace: gpsNamespace} + + Expect(k8sClient.Get(ctx, nsKey, gpsNs)).Should(Succeed()) + + if gpsNs.Annotations == nil { + gpsNs.Annotations = map[string]string{} + } + + gpsNs.Annotations[forceIpWhitelistNamespaceAnnotation] = "example-ip-whitelist" + + Expect(k8sClient.Update(ctx, gpsNs)).Should(Succeed()) + + // create a GPS site as usual + Expect(k8sClient.Create(ctx, gpsSite)).Should(Succeed()) + Eventually(func() bool { + err := k8sClient.Get(ctx, gpsLookupKey, gpsSite) + if err != nil { + return false + } + + return len(gpsSite.Status.Conditions) == 1 && + gpsSite.Status.Conditions[0].Type == webservicescernchv1alpha1.ConditionTypeGitlabPagesSiteCreated && + gpsSite.Status.Conditions[0].Status == metav1.ConditionTrue + }, timeout, interval).Should(BeTrue()) + + route := &routev1.Route{} + routeName := generateRouteName(gpsSite, "my-gps-site.web.cern.ch") + + Expect(k8sClient.Get(ctx, types.NamespacedName{Name: routeName, Namespace: operatorNamespace}, route)).Should(Succeed()) + Expect(route.Annotations["haproxy.router.openshift.io/ip_whitelist"]).To(Equal("example-ip-whitelist")) + }) }) }) diff --git a/controllers/gitlabpagessite_controller.go b/controllers/gitlabpagessite_controller.go index 91cc967745b7055516cfd4c30012ec587fd25fad..c9a5c88ff10970f72ca29a8d2a77e7a640cb44f4 100644 --- a/controllers/gitlabpagessite_controller.go +++ b/controllers/gitlabpagessite_controller.go @@ -47,6 +47,11 @@ const gitlabPagesSiteRouteOwnershipLabel = "staticsites.webservices.cern.ch/stat const gitlabPagesSiteRouteOwnershipAnnotation = "staticsites.webservices.cern.ch/static-site-owner" const userNamespaceLabel = "okd.cern.ch/user-project" +// namespace annotations to override certain behaviors on sites provisioned from resources in that namespace +// (annotations shared with webeos-site-operator) +const forceRouterShardNamespaceAnnotation = "webeos.webservices.cern.ch/force-router-shard" // assign routes to a specific ingress controller +const forceIpWhitelistNamespaceAnnotation = "webeos.webservices.cern.ch/force-ip-whitelist" // set an IP whitelist on routes (e.g. to block access from Internet) + // GitlabPagesSiteReconciler reconciles a GitlabPagesSite object type GitlabPagesSiteReconciler struct { client.Client diff --git a/controllers/operator_methods.go b/controllers/operator_methods.go index 45f0ffd475bf2a43a3a6a8eb5156c1f9cda89d6d..75f1678598185c66049ba859e955c01f75414eed 100644 --- a/controllers/operator_methods.go +++ b/controllers/operator_methods.go @@ -100,6 +100,14 @@ func (r *GitlabPagesSiteReconciler) ensureRoutes(ctx context.Context, gitlabPage if err := r.Get(ctx, types.NamespacedName{Namespace: gitlabPagesSite.Namespace, Name: gitlabPagesSite.Namespace}, namespace); err != nil { return err } + // the router shard to use for the routes associated with this GitlabPagesSite + // the default can be overriden with a namespace annotation (same annotation as for webeos site) + routerShard := namespace.Annotations[forceRouterShardNamespaceAnnotation] + if routerShard == "" { + routerShard = r.DefaultRouterShard + } + // evaluates to an empty string (= allow any IP) if no such annotation on namespace + routeIpWhitelist := namespace.Annotations[forceIpWhitelistNamespaceAnnotation] for _, host := range hosts { route := &routev1.Route{ @@ -122,12 +130,11 @@ func (r *GitlabPagesSiteReconciler) ensureRoutes(ctx context.Context, gitlabPage route.Annotations[gitlabPagesSiteRouteOwnershipAnnotation] = types.NamespacedName{Name: gitlabPagesSite.Name, Namespace: gitlabPagesSite.Namespace}.String() route.Labels[gitlabPagesSiteRouteOwnershipLabel] = generateOwnershipLabelValue(gitlabPagesSite) - if r.RouterShardLabel != "" && r.DefaultRouterShard != "" { - // assign the Route to a specific router shard - if _, ok := route.Labels[r.RouterShardLabel]; !ok { - route.Labels[r.RouterShardLabel] = r.DefaultRouterShard - } + if r.RouterShardLabel != "" && routerShard != "" { + // assign the Route to the desired router shard + route.Labels[r.RouterShardLabel] = routerShard } + route.Annotations["haproxy.router.openshift.io/ip_whitelist"] = routeIpWhitelist // if the host doesn't match the shared domain regex, we will make sure a certificate is automatically provisioned if r.SharedSubdomainRegex != nil && !r.SharedSubdomainRegex.MatchString(string(host)) { @@ -165,7 +172,7 @@ func (r *GitlabPagesSiteReconciler) ensureRoutes(ctx context.Context, gitlabPage TargetPort: intstr.FromInt(r.AuthProxyServicePort), } // If the project is blocked, we redirect the traffic to a non-existing service endpoint - // In this case, HAProxy routers will return a "503 Application not avaiable" message + // In this case, HAProxy routers will return a "503 Application not available" message if projectBlocked(*namespace) { route.Spec.To.Name = blockedServiceName route.Spec.Port.TargetPort = intstr.FromInt(blockedServicePort)