diff --git a/app/scripts/constants.js b/app/scripts/constants.js index 0f905fff86..33fe45dce7 100644 --- a/app/scripts/constants.js +++ b/app/scripts/constants.js @@ -52,6 +52,10 @@ window.OPENSHIFT_CONSTANTS = { // true indicates that deployment metrics should be disabled on the web console overview DISABLE_OVERVIEW_METRICS: false, + // true indicates that none of the routers support wildcard subdomains and + // removes the option from the route creation form. + DISABLE_WILDCARD_ROUTES: true, + // This blacklist hides certain kinds from the "Other Resources" page because they are unpersisted, disallowed for most end users, or not supported by openshift but exist in kubernetes AVAILABLE_KINDS_BLACKLIST: [ // These are k8s kinds that are not supported in the current release of OpenShift diff --git a/app/scripts/controllers/edit/route.js b/app/scripts/controllers/edit/route.js index 6cfe1beea9..a27fabf46e 100644 --- a/app/scripts/controllers/edit/route.js +++ b/app/scripts/controllers/edit/route.js @@ -15,7 +15,8 @@ angular.module('openshiftConsole') AlertMessageService, DataService, Navigate, - ProjectsService) { + ProjectsService, + RoutesService) { $scope.alerts = {}; $scope.renderOptions = { hideFilterWidget: true @@ -51,9 +52,16 @@ angular.module('openshiftConsole') DataService.get("routes", $scope.routeName, context).then( function(original) { route = angular.copy(original); + var host = _.get(route, 'spec.host'); + var isWildcard = _.get(route, 'spec.wildcardPolicy') === 'Subdomain'; + if (isWildcard) { + // Display the route as a wildcard. + host = '*.' + RoutesService.getSubdomain(route); + } $scope.routing = { service: _.get(route, 'spec.to.name'), - host: _.get(route, 'spec.host'), + host: host, + wildcardPolicy: _.get(route, 'spec.wildcardPolicy'), path: _.get(route, 'spec.path'), targetPort: _.get(route, 'spec.port.targetPort'), tls: angular.copy(_.get(route, 'spec.tls')) diff --git a/app/scripts/directives/oscRouting.js b/app/scripts/directives/oscRouting.js index 088e86c787..0abf6a5f66 100644 --- a/app/scripts/directives/oscRouting.js +++ b/app/scripts/directives/oscRouting.js @@ -29,7 +29,7 @@ angular.module("openshiftConsole") * routingDisabled: * An expression that will disable the form (default: false) */ - .directive("oscRouting", function() { + .directive("oscRouting", function(Constants) { return { require: '^form', restrict: 'E', @@ -50,6 +50,17 @@ angular.module("openshiftConsole") link: function(scope, element, attrs, formCtl) { scope.form = formCtl; + scope.disableWildcards = Constants.DISABLE_WILDCARD_ROUTES; + + // Use different patterns for validating hostnames if wildcard subdomains are supported. + if (scope.disableWildcards) { + // See k8s.io/kubernetes/pkg/util/validation/validation.go + scope.hostnamePattern = /^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$/; + } else { + // Allow values like "*.example.com" in addition to the normal hostname regex. + scope.hostnamePattern = /^(\*(\.[a-z0-9]([-a-z0-9]*[a-z0-9]))+|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*)$/; + } + var updatePortOptions = function(service) { if (!service) { return; diff --git a/app/scripts/filters/resources.js b/app/scripts/filters/resources.js index b58108bda3..cbcb34d563 100644 --- a/app/scripts/filters/resources.js +++ b/app/scripts/filters/resources.js @@ -333,10 +333,8 @@ angular.module('openshiftConsole') }) .filter('isWebRoute', function(routeHostFilter) { return function(route){ - //TODO: implement when we can tell if routes are http(s) or not web related which will drive - // links in view - // For now, only return false if the host is not defined. - return !!routeHostFilter(route); + return !!routeHostFilter(route) && + _.get(route, 'spec.wildcardPolicy') !== 'Subdomain'; }; }) .filter('routeWebURL', function(routeHostFilter){ @@ -349,15 +347,21 @@ angular.module('openshiftConsole') return url; }; }) - .filter('routeLabel', function(routeHostFilter, routeWebURLFilter, isWebRouteFilter) { + .filter('routeLabel', function(RoutesService, routeHostFilter, routeWebURLFilter, isWebRouteFilter) { return function(route, host) { if (isWebRouteFilter(route)) { return routeWebURLFilter(route, host); } + var label = (host || routeHostFilter(route)); if (!label) { return ''; } + + if (_.get(route, 'spec.wildcardPolicy') === 'Subdomain') { + label = '*.' + RoutesService.getSubdomain(route); + } + if (route.spec.path) { label += route.spec.path; } diff --git a/app/scripts/services/applicationGenerator.js b/app/scripts/services/applicationGenerator.js index dd3566eb06..cd2d29a94a 100644 --- a/app/scripts/services/applicationGenerator.js +++ b/app/scripts/services/applicationGenerator.js @@ -126,12 +126,19 @@ angular.module("openshiftConsole") to: { kind: "Service", name: serviceName - } + }, + wildcardPolicy: 'None' } }; - if (input.routing.host) { - route.spec.host = input.routing.host; + var host = input.routing.host; + if (host) { + if (host.startsWith('*.')) { + route.spec.wildcardPolicy = 'Subdomain'; + route.spec.host = 'wildcard' + host.substring(1); + } else { + route.spec.host = host; + } } if (input.routing.path) { diff --git a/app/scripts/services/routes.js b/app/scripts/services/routes.js index 5478409cde..07a57f8d55 100644 --- a/app/scripts/services/routes.js +++ b/app/scripts/services/routes.js @@ -63,6 +63,7 @@ angular.module("openshiftConsole") }; var addIngressWarnings = function(route, warnings) { + var wildcardPolicy = _.get(route, 'spec.wildcardPolicy'); angular.forEach(route.status.ingress, function(ingress) { var condition = _.find(ingress.conditions, { type: "Admitted", status: "False" }); if (condition) { @@ -72,6 +73,11 @@ angular.module("openshiftConsole") } warnings.push(message); } + + // This message only displays with old router images that are not aware of `wildcardPolicy`. + if (!condition && wildcardPolicy === 'Subdomain' && ingress.wildcardPolicy !== wildcardPolicy) { + warnings.push('Router "' + ingress.routerName + '" does not support wildcard subdomains. Your route will only be available at host ' + ingress.host + '.'); + } }); }; @@ -123,6 +129,12 @@ angular.module("openshiftConsole") return _.groupBy(routes, 'spec.to.name'); }; + // For host "foo.example.com" return "example.com" + var getSubdomain = function(route) { + var hostname = _.get(route, 'spec.host', ''); + return hostname.replace(/^[a-z0-9]([-a-z0-9]*[a-z0-9])\./, ''); + }; + return { // Gets warnings about a route. // @@ -151,6 +163,7 @@ angular.module("openshiftConsole") getServicePortForRoute: getServicePortForRoute, getPreferredDisplayRoute: getPreferredDisplayRoute, - groupByService: groupByService + groupByService: groupByService, + getSubdomain: getSubdomain }; }); diff --git a/app/styles/_core.less b/app/styles/_core.less index 6385873165..b23ed3410f 100644 --- a/app/styles/_core.less +++ b/app/styles/_core.less @@ -449,12 +449,6 @@ label.checkbox { right: -3px; } -.create-route-icon, -.create-storage-icon { - padding-top: 0; - text-align: right; -} - .about, .command-line { .about-icon { diff --git a/app/styles/_forms.less b/app/styles/_forms.less index afccf05470..a6cb68aced 100644 --- a/app/styles/_forms.less +++ b/app/styles/_forms.less @@ -1,4 +1,8 @@ .copy-to-clipboard input.form-control:read-only { background-color: white; color: @text-color; -} \ No newline at end of file +} + +.input-group-addon.wildcard-prefix { + padding-left: 10px; +} diff --git a/app/styles/_overview.less b/app/styles/_overview.less index fcd9d7de38..7d88168355 100644 --- a/app/styles/_overview.less +++ b/app/styles/_overview.less @@ -246,6 +246,10 @@ margin-left: 2px; margin-right: 6px; } + .non-web-route { + // Add extra margin to account for the missing external link icon. + margin-left: 25px; + } @media (min-width: @screen-sm-min) { // At wider screen widths, always 0 margin. margin: 0; @@ -253,6 +257,9 @@ margin-left: 0; margin-right: 5px; } + .non-web-route { + margin-left: 0; + } } } .app-name { diff --git a/app/views/browse/route.html b/app/views/browse/route.html index 30ff29008b..69cc8dc0c6 100644 --- a/app/views/browse/route.html +++ b/app/views/browse/route.html @@ -43,7 +43,7 @@

- + @@ -59,16 +59,16 @@

{{route | routeLabel}} - Pending + Pending - +
{{route | routeLabel : ingress.host}} {{route | routeLabel : ingress.host}} - + – admission status unknown for router '{{ingress.routerName}}' @@ -77,16 +77,18 @@

rejected by router '{{ingress.routerName}}' - +

+
Wildcard Policy:
+
{{route.spec.wildcardPolicy}}
Path:
{{route.spec.path}} none
-
{{route.spec.to.kind || "Routes to"}}:
+
{{route.spec.to.kind || "Routes To"}}:
{{route.spec.to.name}}
@@ -143,7 +145,7 @@

Traffic

TLS Settings

-
Termination type:
+
Termination Type:
{{route.spec.tls.termination}}
Insecure Traffic:
{{route.spec.tls.insecureEdgeTerminationPolicy || 'None'}}
diff --git a/app/views/directives/osc-routing.html b/app/views/directives/osc-routing.html index b80c870c04..dd06b58ec5 100644 --- a/app/views/directives/osc-routing.html +++ b/app/views/directives/osc-routing.html @@ -42,7 +42,7 @@ type="text" name="host" ng-model="route.host" - ng-pattern="/^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$/" + ng-pattern="hostnamePattern" ng-maxlength="253" ng-readonly="hostReadOnly" placeholder="www.example.com" @@ -52,15 +52,23 @@ aria-describedby="route-host-help">
- Public hostname for the route. - If not specified, a hostname is generated. - The hostname can't be changed after the route is created. +

+ Public hostname for the route. + + If not specified, a hostname is generated. + + + You can use *.example.com with routers that support wildcard subdomains. + +

+

The hostname can't be changed after the route is created.

Hostname must consist of lower-case letters, numbers, periods, and hyphens. It must start and end with a letter or number. + Wildcard subdomains may start with *.
@@ -152,9 +160,10 @@

Alternate Services

-
+
Routes can be secured using several TLS termination types for serving certificates.
diff --git a/app/views/edit/route.html b/app/views/edit/route.html index 7865186b0e..8aeab6cb0d 100644 --- a/app/views/edit/route.html +++ b/app/views/edit/route.html @@ -9,35 +9,31 @@
-
+
-
-
-

Edit Route {{routeName}}

-
- Loading... -
-
-
- - -
- - Cancel -
-
-
-
+

Edit Route {{routeName}}

+
+ Loading...
+
+
+ + +
+ + Cancel +
+
+
diff --git a/app/views/overview/_service-group.html b/app/views/overview/_service-group.html index 0fb7a804cb..f9f50fe6cf 100644 --- a/app/views/overview/_service-group.html +++ b/app/views/overview/_service-group.html @@ -21,7 +21,7 @@

{{displayRoute | routeLabel}} - {{displayRoute | routeLabel}} + {{displayRoute | routeLabel}} diff --git a/dist/scripts/scripts.js b/dist/scripts/scripts.js index f37d66c7ae..d75e9dbcb6 100644 --- a/dist/scripts/scripts.js +++ b/dist/scripts/scripts.js @@ -42,6 +42,7 @@ CLI:{ }, DEFAULT_HPA_CPU_TARGET_PERCENT:80, DISABLE_OVERVIEW_METRICS:!1, +DISABLE_WILDCARD_ROUTES:!0, AVAILABLE_KINDS_BLACKLIST:[ "Binding", "Ingress", "DeploymentConfigRollback" ], ENABLE_TECH_PREVIEW_FEATURE:{ pipelines:!0 @@ -1718,16 +1719,17 @@ spec:{ to:{ kind:"Service", name:c +}, +wildcardPolicy:"None" } -} -}; -a.routing.host && (d.spec.host = a.routing.host), a.routing.path && (d.spec.path = a.routing.path), a.routing.targetPort && (d.spec.port = { +}, e = a.routing.host; +e && (e.startsWith("*.") ? (d.spec.wildcardPolicy = "Subdomain", d.spec.host = "wildcard" + e.substring(1)) :d.spec.host = e), a.routing.path && (d.spec.path = a.routing.path), a.routing.targetPort && (d.spec.port = { targetPort:a.routing.targetPort }); -var e = a.routing.tls; -return e && e.termination && (d.spec.tls = { -termination:e.termination -}, "passthrough" !== e.termination && ("edge" === e.termination && e.insecureEdgeTerminationPolicy && (d.spec.tls.insecureEdgeTerminationPolicy = e.insecureEdgeTerminationPolicy), e.certificate && (d.spec.tls.certificate = e.certificate), e.key && (d.spec.tls.key = e.key), e.caCertificate && (d.spec.tls.caCertificate = e.caCertificate), e.destinationCACertificate && "reencrypt" === e.termination && (d.spec.tls.destinationCACertificate = e.destinationCACertificate))), d; +var f = a.routing.tls; +return f && f.termination && (d.spec.tls = { +termination:f.termination +}, "passthrough" !== f.termination && ("edge" === f.termination && f.insecureEdgeTerminationPolicy && (d.spec.tls.insecureEdgeTerminationPolicy = f.insecureEdgeTerminationPolicy), f.certificate && (d.spec.tls.certificate = f.certificate), f.key && (d.spec.tls.key = f.key), f.caCertificate && (d.spec.tls.caCertificate = f.caCertificate), f.destinationCACertificate && "reencrypt" === f.termination && (d.spec.tls.destinationCACertificate = f.destinationCACertificate))), d; }, f._generateDeploymentConfig = function(a, b, c) { var d = []; angular.forEach(a.deploymentConfig.envVars, function(a, b) { @@ -3039,15 +3041,17 @@ g || (b(f) ? e.push('Route target port is set to "' + f + '", but service "' + d }, e = function(a, b) { a.spec.tls && (a.spec.tls.termination || b.push("Route has a TLS configuration, but no TLS termination type is specified. TLS will not be enabled until a termination type is set."), "passthrough" === a.spec.tls.termination && a.spec.path && b.push('Route path "' + a.spec.path + '" will be ignored since the route uses passthrough termination.')); }, f = function(a, b) { +var c = _.get(a, "spec.wildcardPolicy"); angular.forEach(a.status.ingress, function(a) { -var c = _.find(a.conditions, { +var d = _.find(a.conditions, { type:"Admitted", status:"False" }); -if (c) { -var d = "Requested host " + (a.host || "") + " was rejected by the router."; -(c.message || c.reason) && (d += " Reason: " + (c.message || c.reason) + "."), b.push(d); +if (d) { +var e = "Requested host " + (a.host || "") + " was rejected by the router."; +(d.message || d.reason) && (e += " Reason: " + (d.message || d.reason) + "."), b.push(e); } +d || "Subdomain" !== c || a.wildcardPolicy === c || b.push('Router "' + a.routerName + '" does not support wildcard subdomains. Your route will only be available at host ' + a.host + "."); }); }, g = function(a) { return _.some(a.status.ingress, function(a) { @@ -3068,6 +3072,9 @@ var c = i(a), d = i(b); return d > c ? b :a; }, k = function(a) { return _.groupBy(a, "spec.to.name"); +}, l = function(a) { +var b = _.get(a, "spec.host", ""); +return b.replace(/^[a-z0-9]([-a-z0-9]*[a-z0-9])\./, ""); }; return { getRouteWarnings:function(a, b) { @@ -3076,7 +3083,8 @@ return a ? ("Service" === a.spec.to.kind && d(a, b, c), e(a, c), f(a, c), c) :c; }, getServicePortForRoute:c, getPreferredDisplayRoute:j, -groupByService:k +groupByService:k, +getSubdomain:l }; } ]), angular.module("openshiftConsole").factory("ChartsService", [ "Logger", function(a) { return { @@ -6929,7 +6937,7 @@ d.disableInputs = !1, m(k + " could not be updated.", l(a)); m(k + " could not be loaded.", l(a)); }); })); -} ]), angular.module("openshiftConsole").controller("EditRouteController", [ "$filter", "$location", "$routeParams", "$scope", "AlertMessageService", "DataService", "Navigate", "ProjectsService", function(a, b, c, d, e, f, g, h) { +} ]), angular.module("openshiftConsole").controller("EditRouteController", [ "$filter", "$location", "$routeParams", "$scope", "AlertMessageService", "DataService", "Navigate", "ProjectsService", "RoutesService", function(a, b, c, d, e, f, g, h, i) { d.alerts = {}, d.renderOptions = { hideFilterWidget:!0 }, d.projectName = c.project, d.routeName = c.route, d.loading = !0, d.routeURL = g.resourceURL(d.routeName, "Route", d.projectName), d.breadcrumbs = [ { @@ -6945,20 +6953,23 @@ link:d.routeURL title:"Edit" } ], h.get(c.project).then(_.spread(function(c, h) { d.project = c, d.breadcrumbs[0].title = a("displayName")(c); -var i, j = a("orderByDisplayName"); +var j, k = a("orderByDisplayName"); f.get("routes", d.routeName, h).then(function(a) { -i = angular.copy(a), d.routing = { -service:_.get(i, "spec.to.name"), -host:_.get(i, "spec.host"), -path:_.get(i, "spec.path"), -targetPort:_.get(i, "spec.port.targetPort"), -tls:angular.copy(_.get(i, "spec.tls")) +j = angular.copy(a); +var b = _.get(j, "spec.host"), c = "Subdomain" === _.get(j, "spec.wildcardPolicy"); +c && (b = "*." + i.getSubdomain(j)), d.routing = { +service:_.get(j, "spec.to.name"), +host:b, +wildcardPolicy:_.get(j, "spec.wildcardPolicy"), +path:_.get(j, "spec.path"), +targetPort:_.get(j, "spec.port.targetPort"), +tls:angular.copy(_.get(j, "spec.tls")) }, f.list("services", h, function(a) { -var b = a.by("metadata.name"), c = _.get(i, "spec.to", {}); -d.loading = !1, d.services = j(b), d.routing.to = { +var b = a.by("metadata.name"), c = _.get(j, "spec.to", {}); +d.loading = !1, d.services = k(b), d.routing.to = { service:b[c.name], weight:c.weight -}, d.routing.alternateServices = [], _.each(_.get(i, "spec.alternateBackends"), function(a) { +}, d.routing.alternateServices = [], _.each(_.get(j, "spec.alternateBackends"), function(a) { return "Service" !== a.kind ? (g.toErrorPage('Editing routes with non-service targets is unsupported. You can edit the route with the "Edit YAML" action instead.'), !1) :void d.routing.alternateServices.push({ service:b[a.name], weight:a.weight @@ -6968,15 +6979,15 @@ weight:a.weight }, function() { g.toErrorPage("Could not load route " + d.routeName + "."); }); -var k = function() { +var l = function() { var a = _.get(d, "routing.to.service.metadata.name"); -_.set(i, "spec.to.name", a); +_.set(j, "spec.to.name", a); var b = _.get(d, "routing.to.weight"); -isNaN(b) || _.set(i, "spec.to.weight", b), i.spec.path = d.routing.path; +isNaN(b) || _.set(j, "spec.to.weight", b), j.spec.path = d.routing.path; var c = d.routing.targetPort; -c ? _.set(i, "spec.port.targetPort", c) :delete i.spec.port, _.get(d, "routing.tls.termination") ? (i.spec.tls = d.routing.tls, "edge" !== i.spec.tls.termination && delete i.spec.tls.insecureEdgeTerminationPolicy) :delete i.spec.tls; +c ? _.set(j, "spec.port.targetPort", c) :delete j.spec.port, _.get(d, "routing.tls.termination") ? (j.spec.tls = d.routing.tls, "edge" !== j.spec.tls.termination && delete j.spec.tls.insecureEdgeTerminationPolicy) :delete j.spec.tls; var e = _.get(d, "routing.alternateServices", []); -_.isEmpty(e) ? delete i.spec.alternateBackends :i.spec.alternateBackends = _.map(e, function(a) { +_.isEmpty(e) ? delete j.spec.alternateBackends :j.spec.alternateBackends = _.map(e, function(a) { return { kind:"Service", name:_.get(a, "service.metadata.name"), @@ -6985,7 +6996,7 @@ weight:a.weight }); }; d.updateRoute = function() { -d.form.$valid && (d.disableInputs = !0, k(), f.update("routes", d.routeName, i, h).then(function() { +d.form.$valid && (d.disableInputs = !0, l(), f.update("routes", d.routeName, j, h).then(function() { e.addAlert({ name:d.routeName, data:{ @@ -9015,7 +9026,7 @@ d.init(b.find('input[name="key"]'), b.find('input[name="value"]'), b.find("a.add }; } }; -}), angular.module("openshiftConsole").directive("oscRouting", function() { +}), angular.module("openshiftConsole").directive("oscRouting", [ "Constants", function(a) { return { require:"^form", restrict:"E", @@ -9033,51 +9044,51 @@ var b = _.get(a, "route.tls.termination"); return !b || "passthrough" === b; }; } ], -link:function(a, b, c, d) { -a.form = d; -var e = function(b) { -b && (a.unnamedServicePort = 1 === b.spec.ports.length && !b.spec.ports[0].name, b.spec.ports.length && !a.unnamedServicePort ? a.route.portOptions = _.map(b.spec.ports, function(a) { +link:function(b, c, d, e) { +b.form = e, b.disableWildcards = a.DISABLE_WILDCARD_ROUTES, b.disableWildcards ? b.hostnamePattern = /^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$/ :b.hostnamePattern = /^(\*(\.[a-z0-9]([-a-z0-9]*[a-z0-9]))+|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*)$/; +var f = function(a) { +a && (b.unnamedServicePort = 1 === a.spec.ports.length && !a.spec.ports[0].name, a.spec.ports.length && !b.unnamedServicePort ? b.route.portOptions = _.map(a.spec.ports, function(a) { return { port:a.name, label:a.port + " → " + a.targetPort + " (" + a.protocol + ")" }; -}) :a.route.portOptions = []); +}) :b.route.portOptions = []); }; -a.services && !a.route.service && (a.route.service = _.find(a.services)), a.$watch("route.to.service", function(b, c) { -e(b), b === c && a.route.targetPort || (a.route.targetPort = _.get(a, "route.portOptions[0].port")), a.services && (a.alternateServiceOptions = _.reject(a.services, function(a) { -return b === a; +b.services && !b.route.service && (b.route.service = _.find(b.services)), b.$watch("route.to.service", function(a, c) { +f(a), a === c && b.route.targetPort || (b.route.targetPort = _.get(b, "route.portOptions[0].port")), b.services && (b.alternateServiceOptions = _.reject(b.services, function(b) { +return a === b; })); -}), a.$watch("route.alternateServices", function(b) { -a.duplicateServices = _(b).map("service").filter(function(a, b, c) { +}), b.$watch("route.alternateServices", function(a) { +b.duplicateServices = _(a).map("service").filter(function(a, b, c) { return _.includes(c, a, b + 1); -}).value(), d.$setValidity("duplicateServices", !a.duplicateServices.length); +}).value(), e.$setValidity("duplicateServices", !b.duplicateServices.length); }, !0); -var f = function() { -return !!a.route.tls && ((!a.route.tls.termination || "passthrough" === a.route.tls.termination) && (a.route.tls.certificate || a.route.tls.key || a.route.tls.caCertificate || a.route.tls.destinationCACertificate)); +var g = function() { +return !!b.route.tls && ((!b.route.tls.termination || "passthrough" === b.route.tls.termination) && (b.route.tls.certificate || b.route.tls.key || b.route.tls.caCertificate || b.route.tls.destinationCACertificate)); }; -a.$watch("route.tls.termination", function() { -a.secureRoute = !!_.get(a, "route.tls.termination"), a.showCertificatesNotUsedWarning = f(); +b.$watch("route.tls.termination", function() { +b.secureRoute = !!_.get(b, "route.tls.termination"), b.showCertificatesNotUsedWarning = g(); }); -var g; -a.$watch("secureRoute", function(b, c) { -if (b !== c) { -var d = _.get(a, "route.tls.termination"); -!a.securetRoute && d && (g = d, delete a.route.tls.termination), a.secureRoute && !d && _.set(a, "route.tls.termination", g || "edge"); +var h; +b.$watch("secureRoute", function(a, c) { +if (a !== c) { +var d = _.get(b, "route.tls.termination"); +!b.securetRoute && d && (h = d, delete b.route.tls.termination), b.secureRoute && !d && _.set(b, "route.tls.termination", h || "edge"); } -}), a.addAlternateService = function() { -a.route.alternateServices = a.route.alternateServices || []; -var b = _.find(a.services, function(b) { -return b !== a.route.to.service && !_.some(a.route.alternateServices, { -service:b +}), b.addAlternateService = function() { +b.route.alternateServices = b.route.alternateServices || []; +var a = _.find(b.services, function(a) { +return a !== b.route.to.service && !_.some(b.route.alternateServices, { +service:a }); }); -a.route.alternateServices.push({ -service:b +b.route.alternateServices.push({ +service:a }); }; } }; -}).directive("oscRoutingService", function() { +} ]).directive("oscRoutingService", function() { return { restrict:"E", scope:{ @@ -12548,18 +12559,18 @@ namespace:e }; } ]).filter("isWebRoute", [ "routeHostFilter", function(a) { return function(b) { -return !!a(b); +return !!a(b) && "Subdomain" !== _.get(b, "spec.wildcardPolicy"); }; } ]).filter("routeWebURL", [ "routeHostFilter", function(a) { return function(b, c) { var d = b.spec.tls && "" !== b.spec.tls.tlsTerminationType ? "https" :"http", e = d + "://" + (c || a(b)); return b.spec.path && (e += b.spec.path), e; }; -} ]).filter("routeLabel", [ "routeHostFilter", "routeWebURLFilter", "isWebRouteFilter", function(a, b, c) { -return function(d, e) { -if (c(d)) return b(d, e); -var f = e || a(d); -return f ? (d.spec.path && (f += d.spec.path), f) :""; +} ]).filter("routeLabel", [ "RoutesService", "routeHostFilter", "routeWebURLFilter", "isWebRouteFilter", function(a, b, c, d) { +return function(e, f) { +if (d(e)) return c(e, f); +var g = f || b(e); +return g ? ("Subdomain" === _.get(e, "spec.wildcardPolicy") && (g = "*." + a.getSubdomain(e)), e.spec.path && (g += e.spec.path), g) :""; }; } ]).filter("parameterPlaceholder", function() { return function(a) { diff --git a/dist/scripts/templates.js b/dist/scripts/templates.js index fa4d7dcc9f..e96daef56c 100644 --- a/dist/scripts/templates.js +++ b/dist/scripts/templates.js @@ -3050,12 +3050,14 @@ angular.module('openshiftConsoleTemplates', []).run(['$templateCache', function( "\n" + "

\n" + "\n" + + "
Wildcard Policy:
\n" + + "
{{route.spec.wildcardPolicy}}
\n" + "
Path:
\n" + "
\n" + "{{route.spec.path}}\n" + "none\n" + "
\n" + - "
{{route.spec.to.kind || \"Routes to\"}}:
\n" + + "
{{route.spec.to.kind || \"Routes To\"}}:
\n" + "
\n" + "{{route.spec.to.name}}\n" + "
\n" + @@ -3112,7 +3114,7 @@ angular.module('openshiftConsoleTemplates', []).run(['$templateCache', function( "
\n" + "

TLS Settings

\n" + "
\n" + - "
Termination type:
\n" + + "
Termination Type:
\n" + "
{{route.spec.tls.termination}}
\n" + "
Insecure Traffic:
\n" + "
{{route.spec.tls.insecureEdgeTerminationPolicy || 'None'}}
\n" + @@ -6820,17 +6822,25 @@ angular.module('openshiftConsoleTemplates', []).run(['$templateCache', function( "
\n" + "\n" + "\n" + - "\n" + + "\n" + "
\n" + "\n" + + "

\n" + "Public hostname for the route.\n" + - "If not specified, a hostname is generated.\n" + - "The hostname can't be changed after the route is created.\n" + + "\n" + + "If not specified, a hostname is generated.\n" + + "\n" + + "\n" + + "You can use *.example.com with routers that support wildcard subdomains.\n" + + "\n" + + "

\n" + + "

The hostname can't be changed after the route is created.

\n" + "
\n" + "
\n" + "
\n" + "\n" + "Hostname must consist of lower-case letters, numbers, periods, and hyphens. It must start and end with a letter or number.\n" + + "Wildcard subdomains may start with *.\n" + "\n" + "
\n" + "
\n" + @@ -6893,9 +6903,10 @@ angular.module('openshiftConsoleTemplates', []).run(['$templateCache', function( "
\n" + "
\n" + "\n" + - "
\n" + + "
\n" + "Routes can be secured using several TLS termination types for serving certificates.\n" + "
\n" + "
\n" + @@ -8179,11 +8190,9 @@ angular.module('openshiftConsoleTemplates', []).run(['$templateCache', function( "
\n" + "
\n" + "
\n" + - "
\n" + + "
\n" + "\n" + "\n" + - "
\n" + - "
\n" + "

Edit Route {{routeName}}

\n" + "
\n" + "Loading...\n" + @@ -8204,8 +8213,6 @@ angular.module('openshiftConsoleTemplates', []).run(['$templateCache', function( "
\n" + "
\n" + "
\n" + - "
\n" + - "
\n" + "
" ); @@ -9653,7 +9660,7 @@ angular.module('openshiftConsoleTemplates', []).run(['$templateCache', function( "\n" + "\n" + "{{displayRoute | routeLabel}}\n" + - "{{displayRoute | routeLabel}}\n" + + "{{displayRoute | routeLabel}}\n" + "\n" + "\n" + "\n" + diff --git a/dist/styles/main.css b/dist/styles/main.css index 8eba87b158..8b84f51f09 100644 --- a/dist/styles/main.css +++ b/dist/styles/main.css @@ -3582,6 +3582,7 @@ to{transform:rotate(359deg)} .btn-flat-default{border:1px solid transparent} .btn-flat-default:focus,.btn-flat-default:hover{background-color:#f7f7f7;border-color:#e7e7e7} .copy-to-clipboard input.form-control:read-only{background-color:#fff;color:#363636} +.input-group-addon.wildcard-prefix{padding-left:10px} .card-pf .image-icon,.card-pf .template-icon{font-size:28px;line-height:1;margin-right:15px;opacity:.38} .card-pf-badge{color:#999;font-size:11px;text-transform:uppercase} .card-pf-title-with-icon{align-items:center;display:flex;margin-bottom:15px} @@ -3831,7 +3832,6 @@ label.checkbox{font-weight:400} @media (max-width:767px){.istag-separator{display:none!important} .osc-form .flow>.flow-block.right .action-inline{margin-left:0} } -.create-route-icon,.create-storage-icon{padding-top:0;text-align:right} .lifecycle-hook{padding-bottom:20px} .lifecycle-hook:first-of-type{padding-top:20px} .lifecycle-hook .read-only-tag-image{padding-bottom:10px} @@ -4378,8 +4378,10 @@ ul.messenger-theme-flat .messenger-message.alert-info .messenger-message-inner:b .overview .service-group-header .route-title{font-weight:300;line-height:1.4;margin:0 0 0 6px} .overview .service-group-header .route-title:only-child{margin:0} .overview .service-group-header .route-title .fa-external-link{margin-left:2px;margin-right:6px} +.overview .service-group-header .route-title .non-web-route{margin-left:25px} @media (min-width:768px){.overview .service-group-header .route-title{margin:0} .overview .service-group-header .route-title .fa-external-link{margin-left:0;margin-right:5px} +.overview .service-group-header .route-title .non-web-route{margin-left:0} } .overview .service-group-header .app-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:300;margin:0;text-transform:uppercase;min-width:130px} .overview .service-group-header .other-routes-msg{display:block;line-height:1.1;margin:3px 25px} diff --git a/test/spec/services/applicationGeneratorSpec.js b/test/spec/services/applicationGeneratorSpec.js index 8d16a76190..76c89b0efb 100644 --- a/test/spec/services/applicationGeneratorSpec.js +++ b/test/spec/services/applicationGeneratorSpec.js @@ -186,6 +186,52 @@ describe("ApplicationGenerator", function(){ name: "theServiceName" }, host: "www.example.com", + wildcardPolicy: "None", + path: "/test", + port: { + targetPort: 'tcp-80' + }, + tls: { + termination: "edge", + insecureEdgeTerminationPolicy: "Redirect", + certificate: "dummy-cert", + key: "dummy-key", + caCertificate: "dummy-ca-cert" + } + } + }); + }); + + it("should generate a wildcard route when wildcard subdomains are set", function(){ + // Add the same labels, annotations, and targetPort as application generator `generate()` + var routeInput = angular.copy(inputTemplate); + routeInput.labels.app = routeInput.name; + routeInput.annotations["openshift.io/generated-by"] = "OpenShiftWebConsole"; + routeInput.routing.targetPort = 'tcp-80'; + routeInput.routing.host = '*.example.com'; + + var route = ApplicationGenerator._generateRoute(routeInput, routeInput.name, "theServiceName"); + expect(route).toEqual({ + kind: "Route", + apiVersion: 'v1', + metadata: { + name: "ruby-hello-world", + labels : { + "foo" : "bar", + "abc" : "xyz", + "app": "ruby-hello-world" + }, + annotations: { + "openshift.io/generated-by": "OpenShiftWebConsole" + } + }, + spec: { + to: { + kind: "Service", + name: "theServiceName" + }, + host: "wildcard.example.com", + wildcardPolicy: "Subdomain", path: "/test", port: { targetPort: 'tcp-80' diff --git a/test/spec/services/routesSpec.js b/test/spec/services/routesSpec.js index 981a215581..2ed6a0afc8 100644 --- a/test/spec/services/routesSpec.js +++ b/test/spec/services/routesSpec.js @@ -27,7 +27,8 @@ describe("RoutesService", function(){ kind: "Service", name: "frontend" }, - host: "www.example.com" + host: "www.example.com", + wildcardPolicy: "None" }, status: { ingress: null @@ -132,7 +133,25 @@ describe("RoutesService", function(){ }]; var warnings = RoutesService.getRouteWarnings(route, serviceTemplate); expect(warnings).toEqual(["Requested host www.example.com was rejected by the router. Reason: route bar already exposes www.example.com and is older."]); - }); + }); + + it("should warn if admitted route wildcardPolicy doesn't match", function() { + var route = angular.copy(routeTemplate); + route.spec.path = '/test'; + route.spec.wildcardPolicy = 'Subdomain'; + route.status.ingress = [{ + host: 'www.example.com', + routerName: 'foo', + conditions: [{ + type: "Admitted", + status: "True", + lastTransitionTime: "2016-02-17T17:18:51Z" + }], + wildcardPolicy: 'None' + }]; + var warnings = RoutesService.getRouteWarnings(route, serviceTemplate); + expect(warnings).toEqual(['Router "foo" does not support wildcard subdomains. Your route will only be available at host www.example.com.']); + }); it("should not warn if there are no problems", function() { var route = angular.copy(routeTemplate); @@ -248,4 +267,33 @@ describe("RoutesService", function(){ expect(preferred).toEqual(secure); }); }); + + describe("#getSubdomain", function() { + var routeTemplate = { + kind: "Route", + apiVersion: 'v1', + metadata: { + name: "ruby-hello-world", + labels : { + "app": "ruby-hello-world" + } + }, + spec: { + to: { + kind: "Service", + name: "frontend" + }, + host: "www.example.com", + wildcardPolicy: "Subdomain" + }, + status: { + ingress: null + } + }; + + it("should return the correct subdomain", function() { + var subdomain = RoutesService.getSubdomain(routeTemplate); + expect(subdomain).toEqual("example.com"); + }); + }); });