diff --git a/app/index.html b/app/index.html index 403207e4ca..ff34a1dff1 100644 --- a/app/index.html +++ b/app/index.html @@ -198,6 +198,7 @@

JavaScript Required

+ diff --git a/app/scripts/constants.js b/app/scripts/constants.js index b4aff7c03d..fd180f6c50 100644 --- a/app/scripts/constants.js +++ b/app/scripts/constants.js @@ -97,6 +97,36 @@ window.OPENSHIFT_CONSTANTS = { // 'openshift' should always be included CREATE_FROM_URL_WHITELIST: ['openshift'], + // Namespaced resources not in this whitelist will be flagged to users as potential concerns in template processing + // and Import YAML/JSON. This typically shouldn't be customized but can be if necessary. + SECURITY_CHECK_WHITELIST: [ + {resource: 'buildconfigs', group: ''}, + {resource: 'builds', group: ''}, + {resource: 'configmaps', group: ''}, + {resource: 'daemonsets', group: 'extensions'}, + {resource: 'deployments', group: 'extensions'}, + {resource: 'deploymentconfigs', group: ''}, + {resource: 'endpoints', group: ''}, + {resource: 'events', group: ''}, + {resource: 'horizontalpodautoscalers', group: 'autoscaling'}, + {resource: 'horizontalpodautoscalers', group: 'extensions'}, + {resource: 'imagestreamimages', group: ''}, + {resource: 'imagestreams', group: ''}, + {resource: 'imagestreamtags', group: ''}, + {resource: 'ingresses', group: 'extensions'}, + {resource: 'jobs', group: 'batch'}, + {resource: 'persistentvolumeclaims', group: ''}, + {resource: 'pods', group: ''}, + {resource: 'podtemplates', group: ''}, + {resource: 'replicasets', group: 'extensions'}, + {resource: 'replicationcontrollers', group: ''}, + {resource: 'routes', group: ''}, + {resource: 'secrets', group: ''}, + {resource: 'serviceaccounts', group: ''}, + {resource: 'services', group: ''}, + {resource: 'statefulsets', group: 'apps'} + ], + // href's will be prefixed with /project/{{projectName}} unless they are absolute URLs PROJECT_NAVIGATION: [ { diff --git a/app/scripts/controllers/newfromtemplate.js b/app/scripts/controllers/newfromtemplate.js index 9e159db41a..cb0aa0bde9 100644 --- a/app/scripts/controllers/newfromtemplate.js +++ b/app/scripts/controllers/newfromtemplate.js @@ -18,6 +18,7 @@ angular.module('openshiftConsole') AlertMessageService, ProjectsService, QuotaService, + SecurityCheckService, $q, $location, TaskList, @@ -42,7 +43,7 @@ angular.module('openshiftConsole') } $scope.alerts = {}; - $scope.quotaAlerts = {}; + $scope.precheckAlerts = {}; $scope.projectName = $routeParams.project; $scope.projectPromise = $.Deferred(); $scope.labels = []; @@ -292,7 +293,7 @@ angular.module('openshiftConsole') modalConfig: function() { return { alerts: alerts, - message: "Problems were detected while checking your application configuration.", + message: "We checked your application for potential problems. Please confirm you still want to create this application.", okButtonText: "Create Anyway", okButtonClass: "btn-danger", cancelButtonText: "Cancel" @@ -305,15 +306,18 @@ angular.module('openshiftConsole') }; var showWarningsOrCreate = function(result) { + var alerts = SecurityCheckService.getSecurityAlerts(processedResources, $scope.projectName); + // Now that all checks are completed, show any Alerts if we need to var quotaAlerts = result.quotaAlerts || []; - var errorAlerts = _.filter(quotaAlerts, {type: 'error'}); + alerts = alerts.concat(quotaAlerts); + var errorAlerts = _.filter(alerts, {type: 'error'}); if (errorAlerts.length) { $scope.disableInputs = false; - $scope.quotaAlerts = quotaAlerts; + $scope.precheckAlerts = alerts; } - else if (quotaAlerts.length) { - launchConfirmationDialog(quotaAlerts); + else if (alerts.length) { + launchConfirmationDialog(alerts); $scope.disableInputs = false; } else { diff --git a/app/scripts/directives/fromFile.js b/app/scripts/directives/fromFile.js index d64636ec62..dea05066f9 100644 --- a/app/scripts/directives/fromFile.js +++ b/app/scripts/directives/fromFile.js @@ -11,7 +11,8 @@ angular.module("openshiftConsole") TaskList, DataService, APIService, - QuotaService) { + QuotaService, + SecurityCheckService) { return { restrict: "E", scope: false, @@ -62,7 +63,7 @@ angular.module("openshiftConsole") modalConfig: function() { return { alerts: alerts, - message: "Problems were detected while checking your application configuration.", + message: "We checked your application for potential problems. Please confirm you still want to create this application.", okButtonText: "Create Anyway", okButtonClass: "btn-danger", cancelButtonText: "Cancel" @@ -75,15 +76,18 @@ angular.module("openshiftConsole") }; var showWarningsOrCreate = function(result){ + var alerts = SecurityCheckService.getSecurityAlerts($scope.createResources, $scope.projectName); + // Now that all checks are completed, show any Alerts if we need to var quotaAlerts = result.quotaAlerts || []; - var errorAlerts = _.filter(quotaAlerts, {type: 'error'}); + alerts = alerts.concat(quotaAlerts); + var errorAlerts = _.filter(alerts, {type: 'error'}); if (errorAlerts.length) { $scope.disableInputs = false; - $scope.alerts = quotaAlerts; + $scope.alerts = alerts; } - else if (quotaAlerts.length) { - launchConfirmationDialog(quotaAlerts); + else if (alerts.length) { + launchConfirmationDialog(alerts); $scope.disableInputs = false; } else { @@ -155,7 +159,7 @@ angular.module("openshiftConsole") if ($scope.errorOccured) { return; } - // If resource if Template and it doesn't exist in the project + // If resource is Template and it doesn't exist in the project if ($scope.createResources.length === 1 && $scope.resourceList[0].kind === "Template") { openTemplateProcessModal(); // Else if any resources already exist diff --git a/app/scripts/services/securityCheck.js b/app/scripts/services/securityCheck.js new file mode 100644 index 0000000000..84f4b7fb38 --- /dev/null +++ b/app/scripts/services/securityCheck.js @@ -0,0 +1,90 @@ +'use strict'; + +angular.module("openshiftConsole") + .factory("SecurityCheckService", function(APIService, $filter, Constants) { + var humanizeKind = $filter('humanizeKind'); + var getSecurityAlerts = function(resources, project) { + var alerts = []; + var clusterScopedResources = []; + var roleBindingResources = []; + var roleResources = []; + var notWhitelistedResources = []; + _.each(resources, function(resource) { + if (!_.get(resource, "kind")) { + // This isn't a valid API object + return; + } + var rgv = APIService.objectToResourceGroupVersion(resource); + var apiInfo = APIService.apiInfo(rgv); + if (!apiInfo.namespaced) { + clusterScopedResources.push(resource); + } + else if (rgv.resource === "rolebindings" && (rgv.group === '' || rgv.group === "rbac.authorization.k8s.io")) { + // If role in the rolebinding is one of the "safe" ones ignore it (view or image puller), otherwise warn + var roleRef = _.get(resource, 'roleRef.name'); + if (roleRef !== 'view' && roleRef !== 'system:image-puller') { + roleBindingResources.push(resource); + } + } + else if (rgv.resource === "roles" && (rgv.group === '' || rgv.group === "rbac.authorization.k8s.io")) { + roleResources.push(resource); + } + else if (!_.find(Constants.SECURITY_CHECK_WHITELIST, {resource: rgv.resource, group: rgv.group})) { + notWhitelistedResources.push(resource); + } + }); + if (clusterScopedResources.length) { + var clusterStrs = _.uniq(_.map(clusterScopedResources, function(resource) { + return humanizeKind(resource.kind); + })); + alerts.push({ + type: 'warning', + message: "This will create resources outside of the project, which might impact all users of the cluster.", + details: "Typically only cluster administrators can create these resources. The cluster-level resources being created are: " + clusterStrs.join(", ") + }); + } + if (roleBindingResources.length) { + var roleBindingStrs = []; + _.each(roleBindingResources, function(resource){ + _.each(resource.subjects, function(subject) { + var str = humanizeKind(subject.kind) + " "; + if (subject.kind === 'ServiceAccount') { + str += (subject.namespace || project) + "/"; + } + str += subject.name; + roleBindingStrs.push(str); + }); + }); + roleBindingStrs = _.uniq(roleBindingStrs); + alerts.push({ + type: 'warning', + message: "This will grant permissions to your project.", + details: "Permissions are being granted to: " + roleBindingStrs.join(", ") + }); + } + if (roleResources.length) { + alerts.push({ + type: 'info', + message: "This will create additional membership roles within the project.", + details: "Admins will be able to grant these custom roles to users, groups, and service accounts." + }); + } + if (notWhitelistedResources.length) { + var notWhitelistStrs = _.uniq(_.map(notWhitelistedResources, function(resource) { + return humanizeKind(resource.kind); + })); + alerts.push({ + type: 'warning', + message: "This will create resources that may have security or project behavior implications.", + details: "Make sure you understand what they do before creating them. The resources being created are: " + notWhitelistStrs.join(", ") + }); + } + return alerts; + }; + + return { + // Gets security alerts relevant to a set of resources + // Returns: Array of alerts + getSecurityAlerts: getSecurityAlerts + }; + }); diff --git a/app/views/newfromtemplate.html b/app/views/newfromtemplate.html index 875e3d1014..12317b3322 100644 --- a/app/views/newfromtemplate.html +++ b/app/views/newfromtemplate.html @@ -51,7 +51,7 @@

Images

can-toggle="false" help-text="Each label is applied to each created resource."> - +
Cancel diff --git a/bower.json b/bower.json index 9113308430..c6538563be 100644 --- a/bower.json +++ b/bower.json @@ -47,7 +47,7 @@ "angular-utf8-base64": "0.0.5", "file-saver": "1.3.3", "bootstrap-switch": "3.3.3", - "origin-web-common": "0.0.3" + "origin-web-common": "0.0.5" }, "devDependencies": { "angular-mocks": "1.5.11", diff --git a/dist/scripts/scripts.js b/dist/scripts/scripts.js index 25e96da654..0017726fc1 100644 --- a/dist/scripts/scripts.js +++ b/dist/scripts/scripts.js @@ -63,6 +63,82 @@ name:"jenkins-pipeline-example", namespace:"openshift" }, CREATE_FROM_URL_WHITELIST:[ "openshift" ], +SECURITY_CHECK_WHITELIST:[ { +resource:"buildconfigs", +group:"" +}, { +resource:"builds", +group:"" +}, { +resource:"configmaps", +group:"" +}, { +resource:"daemonsets", +group:"extensions" +}, { +resource:"deployments", +group:"extensions" +}, { +resource:"deploymentconfigs", +group:"" +}, { +resource:"endpoints", +group:"" +}, { +resource:"events", +group:"" +}, { +resource:"horizontalpodautoscalers", +group:"autoscaling" +}, { +resource:"horizontalpodautoscalers", +group:"extensions" +}, { +resource:"imagestreamimages", +group:"" +}, { +resource:"imagestreams", +group:"" +}, { +resource:"imagestreamtags", +group:"" +}, { +resource:"ingresses", +group:"extensions" +}, { +resource:"jobs", +group:"batch" +}, { +resource:"persistentvolumeclaims", +group:"" +}, { +resource:"pods", +group:"" +}, { +resource:"podtemplates", +group:"" +}, { +resource:"replicasets", +group:"extensions" +}, { +resource:"replicationcontrollers", +group:"" +}, { +resource:"routes", +group:"" +}, { +resource:"secrets", +group:"" +}, { +resource:"serviceaccounts", +group:"" +}, { +resource:"services", +group:"" +}, { +resource:"statefulsets", +group:"apps" +} ], PROJECT_NAVIGATION:[ { label:"Overview", iconClass:"fa fa-dashboard", @@ -2786,6 +2862,62 @@ getQuotaAlerts:r, getLatestQuotaAlerts:s, isAnyQuotaExceeded:t }; +} ]), angular.module("openshiftConsole").factory("SecurityCheckService", [ "APIService", "$filter", "Constants", function(a, b, c) { +var d = b("humanizeKind"), e = function(b, e) { +var f = [], g = [], h = [], i = [], j = []; +if (_.each(b, function(b) { +if (_.get(b, "kind")) { +var d = a.objectToResourceGroupVersion(b), e = a.apiInfo(d); +if (e.namespaced) if ("rolebindings" !== d.resource || "" !== d.group && "rbac.authorization.k8s.io" !== d.group) "roles" !== d.resource || "" !== d.group && "rbac.authorization.k8s.io" !== d.group ? _.find(c.SECURITY_CHECK_WHITELIST, { +resource:d.resource, +group:d.group +}) || j.push(b) :i.push(b); else { +var f = _.get(b, "roleRef.name"); +"view" !== f && "system:image-puller" !== f && h.push(b); +} else g.push(b); +} +}), g.length) { +var k = _.uniq(_.map(g, function(a) { +return d(a.kind); +})); +f.push({ +type:"warning", +message:"This will create resources outside of the project, which might impact all users of the cluster.", +details:"Typically only cluster administrators can create these resources. The cluster-level resources being created are: " + k.join(", ") +}); +} +if (h.length) { +var l = []; +_.each(h, function(a) { +_.each(a.subjects, function(a) { +var b = d(a.kind) + " "; +"ServiceAccount" === a.kind && (b += (a.namespace || e) + "/"), b += a.name, l.push(b); +}); +}), l = _.uniq(l), f.push({ +type:"warning", +message:"This will grant permissions to your project.", +details:"Permissions are being granted to: " + l.join(", ") +}); +} +if (i.length && f.push({ +type:"info", +message:"This will create additional membership roles within the project.", +details:"Admins will be able to grant these custom roles to users, groups, and service accounts." +}), j.length) { +var m = _.uniq(_.map(j, function(a) { +return d(a.kind); +})); +f.push({ +type:"warning", +message:"This will create resources that may have security or project behavior implications.", +details:"Make sure you understand what they do before creating them. The resources being created are: " + m.join(", ") +}); +} +return f; +}; +return { +getSecurityAlerts:e +}; } ]), angular.module("openshiftConsole").factory("LabelsService", function() { var a = function(a) { return _.get(a, "spec.template", { @@ -7320,10 +7452,10 @@ a.showParamsTable = !0; d.unwatchAll(p); }); })); -} ]), angular.module("openshiftConsole").controller("NewFromTemplateController", [ "$scope", "$http", "$routeParams", "DataService", "ProcessedTemplateService", "AlertMessageService", "ProjectsService", "QuotaService", "$q", "$location", "TaskList", "$parse", "Navigate", "$filter", "$uibModal", "imageObjectRefFilter", "failureObjectNameFilter", "CachedTemplateService", "keyValueEditorUtils", "Constants", function(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t) { -var u = c.template, v = c.namespace || ""; -if (!u) return void m.toErrorPage("Cannot create from template: a template name was not specified."); -a.alerts = {}, a.quotaAlerts = {}, a.projectName = c.project, a.projectPromise = $.Deferred(), a.labels = [], a.systemLabels = [], a.breadcrumbs = [ { +} ]), angular.module("openshiftConsole").controller("NewFromTemplateController", [ "$scope", "$http", "$routeParams", "DataService", "ProcessedTemplateService", "AlertMessageService", "ProjectsService", "QuotaService", "SecurityCheckService", "$q", "$location", "TaskList", "$parse", "Navigate", "$filter", "$uibModal", "imageObjectRefFilter", "failureObjectNameFilter", "CachedTemplateService", "keyValueEditorUtils", "Constants", function(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u) { +var v = c.template, w = c.namespace || ""; +if (!v) return void n.toErrorPage("Cannot create from template: a template name was not specified."); +a.alerts = {}, a.precheckAlerts = {}, a.projectName = c.project, a.projectPromise = $.Deferred(), a.labels = [], a.systemLabels = [], a.breadcrumbs = [ { title:a.projectName, link:"project/" + a.projectName }, { @@ -7333,11 +7465,11 @@ link:"project/" + a.projectName + "/create" title:"Catalog", link:"project/" + a.projectName + "/create?tab=fromCatalog" }, { -title:u +title:v } ], a.alerts = a.alerts || {}, f.getAlerts().forEach(function(b) { a.alerts[b.name] = b.data; }), f.clearAlerts(); -var w = n("displayName"), x = n("humanize"), y = l("spec.template.spec.containers"), z = l("spec.strategy.sourceStrategy.from || spec.strategy.dockerStrategy.from || spec.strategy.customStrategy.from"), A = l("spec.output.to"), B = function() { +var x = o("displayName"), y = o("humanize"), z = m("spec.template.spec.containers"), A = m("spec.strategy.sourceStrategy.from || spec.strategy.dockerStrategy.from || spec.strategy.customStrategy.from"), B = m("spec.output.to"), C = function() { try { return JSON.parse(c.templateParamsMap); } catch (b) { @@ -7356,22 +7488,22 @@ return _.includes(c, b.name); }); return _.get(d, "imageChangeParams.from.name"); } -function l(a) { -for (var b = [], c = G.exec(a); c; ) b.push(c[1]), c = G.exec(a); +function m(a) { +for (var b = [], c = H.exec(a); c; ) b.push(c[1]), c = H.exec(a); return b; } -function q() { +function r() { var b = {}; return _.each(a.template.parameters, function(a) { b[a.name] = a.value; }), b; } -function t() { -var b = q(); -a.templateImages = _.map(H, function(a) { +function u() { +var b = r(); +a.templateImages = _.map(I, function(a) { if (_.isEmpty(a.usesParameters)) return a; var c = _.template(a.name, { -interpolate:G +interpolate:H }); return { name:c(b), @@ -7379,35 +7511,35 @@ usesParameters:a.usesParameters }; }); } -function C(a) { -var b = [], c = y(a); +function D(a) { +var b = [], c = z(a); return c && angular.forEach(c, function(c) { var d = c.image, e = g(a, c); e && (d = e), d && b.push(d); }), b; } -function D(b) { -H = []; +function E(b) { +I = []; var c = [], d = {}; angular.forEach(b.objects, function(b) { if ("BuildConfig" === b.kind) { -var e = p(z(b), a.projectName); -e && H.push({ +var e = q(A(b), a.projectName); +e && I.push({ name:e, -usesParameters:l(e) +usesParameters:m(e) }); -var f = p(A(b), a.projectName); +var f = q(B(b), a.projectName); f && (d[f] = !0); } -"DeploymentConfig" === b.kind && (c = c.concat(C(b))); +"DeploymentConfig" === b.kind && (c = c.concat(D(b))); }), c.forEach(function(a) { -d[a] || H.push({ +d[a] || I.push({ name:a, -usesParameters:l(a) +usesParameters:m(a) }); -}), H = _.uniq(H, !1, "name"); +}), I = _.uniq(I, !1, "name"); } -function E(a) { +function F(a) { var b = /^helplink\.(.*)\.title$/, c = /^helplink\.(.*)\.url$/, d = {}; for (var e in a.annotations) { var f, g = e.match(b); @@ -7415,60 +7547,60 @@ g ? (f = d[g[1]] || {}, f.title = a.annotations[e], d[g[1]] = f) :(g = e.match(c } return d; } -function F(b) { +function G(b) { if (a.parameterDisplayNames = {}, _.each(a.template.parameters, function(b) { a.parameterDisplayNames[b.name] = b.displayName || b.name; }), c.templateParamsMap) { -var d = B(); +var d = C(); _.each(a.template.parameters, function(a) { d[a.name] && (a.value = d[a.name]); }); } -D(a.template); +E(a.template); var e = function(a) { return !_.isEmpty(a.usesParameters); }; -_.some(H, e) ? a.$watch("template.parameters", _.debounce(function(b) { -a.$apply(t); +_.some(I, e) ? a.$watch("template.parameters", _.debounce(function(b) { +a.$apply(u); }, 50, { maxWait:250 -}), !0) :a.templateImages = H, a.systemLabels = _.map(a.template.labels, function(a, b) { +}), !0) :a.templateImages = I, a.systemLabels = _.map(a.template.labels, function(a, b) { return { name:b, value:a }; -}), M() && a.systemLabels.push({ +}), N() && a.systemLabels.push({ name:"app", value:a.template.metadata.name }); } -a.project = b, a.breadcrumbs[0].title = n("displayName")(b); -var G = /\${([a-zA-Z0-9\_]+)}/g, H = []; +a.project = b, a.breadcrumbs[0].title = o("displayName")(b); +var H = /\${([a-zA-Z0-9\_]+)}/g, I = []; a.projectDisplayName = function() { -return w(this.project) || this.projectName; +return x(this.project) || this.projectName; }, a.templateDisplayName = function() { -return w(this.template); +return x(this.template); }; -var I, J = function() { +var J, K = function() { var b = { started:"Creating " + a.templateDisplayName() + " in project " + a.projectDisplayName(), success:"Created " + a.templateDisplayName() + " in project " + a.projectDisplayName(), failure:"Failed to create " + a.templateDisplayName() + " in project " + a.projectDisplayName() -}, e = E(a.template); -k.clear(), k.add(b, e, c.project, function() { -var b = i.defer(); -return d.batch(I, f).then(function(c) { +}, e = F(a.template); +l.clear(), l.add(b, e, c.project, function() { +var b = j.defer(); +return d.batch(J, f).then(function(c) { var d = [], e = !1; c.failure.length > 0 ? (e = !0, c.failure.forEach(function(a) { d.push({ type:"error", -message:"Cannot create " + x(a.object.kind).toLowerCase() + ' "' + a.object.metadata.name + '". ', +message:"Cannot create " + y(a.object.kind).toLowerCase() + ' "' + a.object.metadata.name + '". ', details:a.data.message }); }), c.success.forEach(function(a) { d.push({ type:"success", -message:"Created " + x(a.kind).toLowerCase() + ' "' + a.metadata.name + '" successfully. ' +message:"Created " + y(a.kind).toLowerCase() + ' "' + a.metadata.name + '" successfully. ' }); })) :d.push({ type:"success", @@ -7478,9 +7610,9 @@ alerts:d, hasErrors:e }); }), b.promise; -}), m.toNextSteps(u, a.projectName); -}, K = function(a) { -var b = o.open({ +}), n.toNextSteps(v, a.projectName); +}, L = function(a) { +var b = p.open({ animation:!0, templateUrl:"views/modals/confirm.html", controller:"ConfirmModalController", @@ -7488,7 +7620,7 @@ resolve:{ modalConfig:function() { return { alerts:a, -message:"Problems were detected while checking your application configuration.", +message:"We checked your application for potential problems. Please confirm you still want to create this application.", okButtonText:"Create Anyway", okButtonClass:"btn-danger", cancelButtonText:"Cancel" @@ -7496,18 +7628,20 @@ cancelButtonText:"Cancel" } } }); -b.result.then(J); -}, L = function(b) { -var c = b.quotaAlerts || [], d = _.filter(c, { +b.result.then(K); +}, M = function(b) { +var c = i.getSecurityAlerts(J, a.projectName), d = b.quotaAlerts || []; +c = c.concat(d); +var e = _.filter(c, { type:"error" }); -d.length ? (a.disableInputs = !1, a.quotaAlerts = c) :c.length ? (K(c), a.disableInputs = !1) :J(); +e.length ? (a.disableInputs = !1, a.precheckAlerts = c) :c.length ? (L(c), a.disableInputs = !1) :K(); }; a.createFromTemplate = function() { a.disableInputs = !0; -var b = s.mapEntries(s.compactEntries(a.labels)), c = s.mapEntries(s.compactEntries(a.systemLabels)); +var b = t.mapEntries(t.compactEntries(a.labels)), c = t.mapEntries(t.compactEntries(a.systemLabels)); a.template.labels = _.extend(c, b), d.create("processedtemplates", null, a.template, f).then(function(b) { -e.setTemplateData(b.parameters, a.template.parameters, b.message), I = b.objects, h.getLatestQuotaAlerts(I, f).then(L); +e.setTemplateData(b.parameters, a.template.parameters, b.message), J = b.objects, h.getLatestQuotaAlerts(J, f).then(M); }, function(b) { a.disableInputs = !1; var c; @@ -7518,24 +7652,24 @@ details:c }; }); }; -var M = function() { +var N = function() { return !_.get(a.template, "labels.app") && !_.some(a.template.objects, "metadata.labels.app"); }; -if (v) d.get("templates", u, { -namespace:v || a.projectName +if (w) d.get("templates", v, { +namespace:w || a.projectName }).then(function(b) { -a.template = b, F(), a.breadcrumbs[3].title = n("displayName")(b); +a.template = b, G(), a.breadcrumbs[3].title = o("displayName")(b); }, function() { -m.toErrorPage("Cannot create from template: the specified template could not be retrieved."); +n.toErrorPage("Cannot create from template: the specified template could not be retrieved."); }); else { -if (a.template = r.getTemplate(), _.isEmpty(a.template)) { -var N = URI("error").query({ +if (a.template = s.getTemplate(), _.isEmpty(a.template)) { +var O = URI("error").query({ error:"not_found", error_description:"Template wasn't found in cache." }).toString(); -j.url(N); +k.url(O); } -r.clearTemplate(), F(); +s.clearTemplate(), G(); } })); } ]), angular.module("openshiftConsole").controller("LabelsController", [ "$scope", function(a) { @@ -8717,84 +8851,84 @@ b.unwatchAll(e); }); } ] }; -} ]), angular.module("openshiftConsole").directive("fromFile", [ "$q", "$uibModal", "$location", "$filter", "CachedTemplateService", "AlertMessageService", "Navigate", "TaskList", "DataService", "APIService", "QuotaService", function(a, b, c, d, e, f, g, h, i, j, k) { +} ]), angular.module("openshiftConsole").directive("fromFile", [ "$q", "$uibModal", "$location", "$filter", "CachedTemplateService", "AlertMessageService", "Navigate", "TaskList", "DataService", "APIService", "QuotaService", "SecurityCheckService", function(a, b, c, d, e, f, g, h, i, j, k, l) { return { restrict:"E", scope:!1, templateUrl:"views/directives/from-file.html", -controller:[ "$scope", function(l) { -function m(a) { -return !!a.kind || (l.error = { +controller:[ "$scope", function(m) { +function n(a) { +return !!a.kind || (m.error = { message:"Resource is missing kind field." }, !1); } -function n(a) { -return !!l.isList || (a.metadata ? a.metadata.name ? !a.metadata.namespace || a.metadata.namespace === l.projectName || (l.error = { +function o(a) { +return !!m.isList || (a.metadata ? a.metadata.name ? !a.metadata.namespace || a.metadata.namespace === m.projectName || (m.error = { message:a.kind + " " + a.metadata.name + " can't be created in project " + a.metadata.namespace + ". Can't create resource in different projects." -}, !1) :(l.error = { +}, !1) :(m.error = { message:"Resource name is missing in metadata field." -}, !1) :(l.error = { +}, !1) :(m.error = { message:"Resource is missing metadata field." }, !1)); } -function o() { +function p() { var a = b.open({ animation:!0, templateUrl:"views/modals/process-template.html", controller:"ProcessTemplateModalController", -scope:l +scope:m }); a.result.then(function() { -l.templateOptions.add ? q() :(e.setTemplate(l.resourceList[0]), r()); +m.templateOptions.add ? r() :(e.setTemplate(m.resourceList[0]), s()); }); } -function p() { +function q() { var a = b.open({ animation:!0, templateUrl:"views/modals/confirm-replace.html", controller:"ConfirmReplaceModalController", -scope:l +scope:m }); a.result.then(function() { -k.getLatestQuotaAlerts(l.createResources, l.context).then(C); +k.getLatestQuotaAlerts(m.createResources, m.context).then(D); }); } -function q() { -var b = l.createResources.length, c = l.updateResources.length; -if (l.resourceKind.endsWith("List")) { +function r() { +var b = m.createResources.length, c = m.updateResources.length; +if (m.resourceKind.endsWith("List")) { var d = []; -c > 0 && d.push(v()), b > 0 && d.push(u()), a.all(d).then(r); -} else t(); +c > 0 && d.push(w()), b > 0 && d.push(v()), a.all(d).then(s); +} else u(); } -function r() { +function s() { var a; -if ("Template" === l.resourceKind && l.templateOptions.process && !l.errorOccured) { -var b = l.templateOptions.add || l.updateResources.length > 0 ? l.projectName :""; -a = g.createFromTemplateURL(A, l.projectName, { +if ("Template" === m.resourceKind && m.templateOptions.process && !m.errorOccured) { +var b = m.templateOptions.add || m.updateResources.length > 0 ? m.projectName :""; +a = g.createFromTemplateURL(B, m.projectName, { namespace:b }); -} else a = g.projectOverviewURL(l.projectName); +} else a = g.projectOverviewURL(m.projectName); c.url(a); } -function s(a) { +function t(a) { var b = j.objectToResourceGroupVersion(a); -return b ? j.apiInfo(b) ? i.get(b, a.metadata.name, l.context, { +return b ? j.apiInfo(b) ? i.get(b, a.metadata.name, m.context, { errorNotification:!1 }).then(function(b) { var c = angular.copy(a), d = angular.copy(b.metadata); -d.annotations = a.metadata.annotations, d.labels = a.metadata.labels, c.metadata = d, l.updateResources.push(c); +d.annotations = a.metadata.annotations, d.labels = a.metadata.labels, c.metadata = d, m.updateResources.push(c); }, function() { -l.createResources.push(a); -}) :(l.errorOccured = !0, void (l.error = { +m.createResources.push(a); +}) :(m.errorOccured = !0, void (m.error = { message:j.unsupportedObjectKindOrVersion(a) -})) :(l.errorOccured = !0, void (l.error = { +})) :(m.errorOccured = !0, void (m.error = { message:j.invalidObjectKindOrVersion(a) })); } -function t() { +function u() { var a; -_.isEmpty(l.createResources) ? (a = _.head(l.updateResources), i.update(j.kindToResource(a.kind), a.metadata.name, a, { -namespace:l.projectName +_.isEmpty(m.createResources) ? (a = _.head(m.updateResources), i.update(j.kindToResource(a.kind), a.metadata.name, a, { +namespace:m.projectName }).then(function() { f.addAlert({ name:a.metadata.name, @@ -8802,15 +8936,15 @@ data:{ type:"success", message:a.kind + " " + a.metadata.name + " was successfully updated." } -}), r(); +}), s(); }, function(b) { -l.alerts["update" + a.metadata.name] = { +m.alerts["update" + a.metadata.name] = { type:"error", -message:"Unable to update the " + x(a.kind) + " '" + a.metadata.name + "'.", +message:"Unable to update the " + y(a.kind) + " '" + a.metadata.name + "'.", details:d("getErrorDetails")(b) }; -})) :(a = _.head(l.createResources), i.create(j.kindToResource(a.kind), null, a, { -namespace:l.projectName +})) :(a = _.head(m.createResources), i.create(j.kindToResource(a.kind), null, a, { +namespace:m.projectName }).then(function() { f.addAlert({ name:a.metadata.name, @@ -8818,39 +8952,39 @@ data:{ type:"success", message:a.kind + " " + a.metadata.name + " was successfully created." } -}), r(); +}), s(); }, function(b) { -l.alerts["create" + a.metadata.name] = { +m.alerts["create" + a.metadata.name] = { type:"error", -message:"Unable to create the " + x(a.kind) + " '" + a.metadata.name + "'.", +message:"Unable to create the " + y(a.kind) + " '" + a.metadata.name + "'.", details:d("getErrorDetails")(b) }; })); } -function u() { +function v() { var b = { -started:"Creating resources in project " + l.projectName, -success:"Creating resources in project " + l.projectName, -failure:"Failed to create some resources in project " + l.projectName +started:"Creating resources in project " + m.projectName, +success:"Creating resources in project " + m.projectName, +failure:"Failed to create some resources in project " + m.projectName }, c = {}; -h.add(b, c, l.projectName, function() { +h.add(b, c, m.projectName, function() { var b = a.defer(); -return i.batch(l.createResources, l.context, "create").then(function(a) { +return i.batch(m.createResources, m.context, "create").then(function(a) { var c = [], d = !1; -if (a.failure.length > 0) d = !0, l.errorOccured = !0, a.failure.forEach(function(a) { +if (a.failure.length > 0) d = !0, m.errorOccured = !0, a.failure.forEach(function(a) { c.push({ type:"error", -message:"Cannot create " + x(a.object.kind) + ' "' + a.object.metadata.name + '". ', +message:"Cannot create " + y(a.object.kind) + ' "' + a.object.metadata.name + '". ', details:a.data.message }); }), a.success.forEach(function(a) { c.push({ type:"success", -message:"Created " + x(a.kind) + ' "' + a.metadata.name + '" successfully. ' +message:"Created " + y(a.kind) + ' "' + a.metadata.name + '" successfully. ' }); }); else { var e; -e = l.isList ? "All items in list were created successfully." :x(l.resourceKind) + " " + l.resourceName + " was successfully created.", c.push({ +e = m.isList ? "All items in list were created successfully." :y(m.resourceKind) + " " + m.resourceName + " was successfully created.", c.push({ type:"success", message:e }); @@ -8862,30 +8996,30 @@ hasErrors:d }), b.promise; }); } -function v() { +function w() { var b = { -started:"Updating resources in project " + l.projectName, -success:"Updated resources in project " + l.projectName, -failure:"Failed to update some resources in project " + l.projectName +started:"Updating resources in project " + m.projectName, +success:"Updated resources in project " + m.projectName, +failure:"Failed to update some resources in project " + m.projectName }, c = {}; -h.add(b, c, l.projectName, function() { +h.add(b, c, m.projectName, function() { var b = a.defer(); -return i.batch(l.updateResources, l.context, "update").then(function(a) { +return i.batch(m.updateResources, m.context, "update").then(function(a) { var c = [], d = !1; -if (a.failure.length > 0) d = !0, l.errorOccured = !0, a.failure.forEach(function(a) { +if (a.failure.length > 0) d = !0, m.errorOccured = !0, a.failure.forEach(function(a) { c.push({ type:"error", -message:"Cannot update " + x(a.object.kind) + ' "' + a.object.metadata.name + '". ', +message:"Cannot update " + y(a.object.kind) + ' "' + a.object.metadata.name + '". ', details:a.data.message }); }), a.success.forEach(function(a) { c.push({ type:"success", -message:"Updated " + x(a.kind) + ' "' + a.metadata.name + '" successfully. ' +message:"Updated " + y(a.kind) + ' "' + a.metadata.name + '" successfully. ' }); }); else { var e; -e = l.isList ? "All items in list were updated successfully." :x(l.resourceKind) + " " + l.resourceName + " was successfully updated.", c.push({ +e = m.isList ? "All items in list were updated successfully." :y(m.resourceKind) + " " + m.resourceName + " was successfully updated.", c.push({ type:"success", message:e }); @@ -8906,27 +9040,27 @@ alerts:c }), b.promise; }); } -var w, x = d("humanizeKind"); -h.clear(), l.aceLoaded = function(a) { -w = a.getSession(), w.setOption("tabSize", 2), w.setOption("useSoftTabs", !0), a.setDragDelay = 0, a.$blockScrolling = 1 / 0; +var x, y = d("humanizeKind"); +h.clear(), m.aceLoaded = function(a) { +x = a.getSession(), x.setOption("tabSize", 2), x.setOption("useSoftTabs", !0), a.setDragDelay = 0, a.$blockScrolling = 1 / 0; }; -var y = function() { -var a = w.getAnnotations(); -l.editorErrorAnnotation = _.some(a, { +var z = function() { +var a = x.getAnnotations(); +m.editorErrorAnnotation = _.some(a, { type:"error" }); -}, z = _.debounce(function() { +}, A = _.debounce(function() { try { -JSON.parse(l.editorContent), w.setMode("ace/mode/json"); +JSON.parse(m.editorContent), x.setMode("ace/mode/json"); } catch (a) { try { -jsyaml.safeLoad(l.editorContent), w.setMode("ace/mode/yaml"); +jsyaml.safeLoad(m.editorContent), x.setMode("ace/mode/yaml"); } catch (a) {} } -l.$apply(y); +m.$apply(z); }, 300); -l.aceChanged = z; -var A, B = function(a) { +m.aceChanged = A; +var B, C = function(a) { var c = b.open({ animation:!0, templateUrl:"views/modals/confirm.html", @@ -8935,7 +9069,7 @@ resolve:{ modalConfig:function() { return { alerts:a, -message:"Problems were detected while checking your application configuration.", +message:"We checked your application for potential problems. Please confirm you still want to create this application.", okButtonText:"Create Anyway", okButtonClass:"btn-danger", cancelButtonText:"Cancel" @@ -8943,34 +9077,36 @@ cancelButtonText:"Cancel" } } }); -c.result.then(q); -}, C = function(a) { -var b = a.quotaAlerts || [], c = _.filter(b, { +c.result.then(r); +}, D = function(a) { +var b = l.getSecurityAlerts(m.createResources, m.projectName), c = a.quotaAlerts || []; +b = b.concat(c); +var d = _.filter(b, { type:"error" }); -c.length ? (l.disableInputs = !1, l.alerts = b) :b.length ? (B(b), l.disableInputs = !1) :q(); +d.length ? (m.disableInputs = !1, m.alerts = b) :b.length ? (C(b), m.disableInputs = !1) :r(); }; -l.create = function() { -l.alerts = {}, delete l.error; +m.create = function() { +m.alerts = {}, delete m.error; try { -A = JSON.parse(l.editorContent); +B = JSON.parse(m.editorContent); } catch (b) { try { -A = jsyaml.safeLoad(l.editorContent); +B = jsyaml.safeLoad(m.editorContent); } catch (b) { -return void (l.error = b); +return void (m.error = b); } } -if (m(A) && (l.resourceKind = A.kind, l.resourceKind.endsWith("List") ? l.isList = !0 :l.isList = !1, n(A))) { -l.isList ? (l.resourceList = A.items, l.resourceName = "") :(l.resourceList = [ A ], l.resourceName = A.metadata.name, "Template" === l.resourceKind && (l.templateOptions = { +if (n(B) && (m.resourceKind = B.kind, m.resourceKind.endsWith("List") ? m.isList = !0 :m.isList = !1, o(B))) { +m.isList ? (m.resourceList = B.items, m.resourceName = "") :(m.resourceList = [ B ], m.resourceName = B.metadata.name, "Template" === m.resourceKind && (m.templateOptions = { process:!0, add:!1 -})), l.updateResources = [], l.createResources = []; +})), m.updateResources = [], m.createResources = []; var c = []; -l.errorOccured = !1, _.forEach(l.resourceList, function(a) { -return n(a) ? void c.push(s(a)) :(l.errorOccured = !0, !1); +m.errorOccured = !1, _.forEach(m.resourceList, function(a) { +return o(a) ? void c.push(t(a)) :(m.errorOccured = !0, !1); }), a.all(c).then(function() { -l.errorOccured || (1 === l.createResources.length && "Template" === l.resourceList[0].kind ? o() :_.isEmpty(l.updateResources) ? k.getLatestQuotaAlerts(l.createResources, l.context).then(C) :(l.updateTemplate = 1 === l.updateResources.length && "Template" === l.updateResources[0].kind, l.updateTemplate ? o() :p())); +m.errorOccured || (1 === m.createResources.length && "Template" === m.resourceList[0].kind ? p() :_.isEmpty(m.updateResources) ? k.getLatestQuotaAlerts(m.createResources, m.context).then(D) :(m.updateTemplate = 1 === m.updateResources.length && "Template" === m.updateResources[0].kind, m.updateTemplate ? p() :q())); }); } }; diff --git a/dist/scripts/templates.js b/dist/scripts/templates.js index ebc0569d81..a9c920ca3c 100644 --- a/dist/scripts/templates.js +++ b/dist/scripts/templates.js @@ -11186,7 +11186,7 @@ angular.module('openshiftConsoleTemplates', []).run(['$templateCache', function( "\n" + "\n" + "\n" + - "\n" + + "\n" + "
\n" + "\n" + "Cancel\n" + diff --git a/dist/scripts/vendor.js b/dist/scripts/vendor.js index e2976477e1..4a6e4bf06f 100644 --- a/dist/scripts/vendor.js +++ b/dist/scripts/vendor.js @@ -59557,21 +59557,22 @@ data:a, textStatus:b, xhr:c }); -}), j = e + window.OPENSHIFT_CONFIG.apis.hostPort + window.OPENSHIFT_CONFIG.apis.prefix, k = $.get(j).then(function(a) { -var b = []; -return _.each(a.groups, function(a) { -var e = { -name:a.name, -preferredVersion:a.preferredVersion.version, -versions:{} -}; -c[e.name] = e, _.each(a.versions, function(a) { -var c = a.version; -e.versions[c] = { +}), j = e + window.OPENSHIFT_CONFIG.apis.hostPort + window.OPENSHIFT_CONFIG.apis.prefix, k = function(a, b, e) { +var f = []; +return _.each(e.groups, function(e) { +var g = { +name:e.name, +preferredVersion:e.preferredVersion.version, +versions:{}, +hostPrefix:b +}; +c[g.name] = g, _.each(e.versions, function(b) { +var c = b.version; +g.versions[c] = { version:c, -groupVersion:a.groupVersion -}, b.push($.get(j + "/" + a.groupVersion).done(function(a) { -e.versions[c].resources = _.indexBy(a.resources, "name"); +groupVersion:b.groupVersion +}, f.push($.get(a + "/" + b.groupVersion).done(function(a) { +g.versions[c].resources = _.indexBy(a.resources, "name"); }).fail(function(a, b, c) { d.push({ data:a, @@ -59580,17 +59581,28 @@ xhr:c }); })); }); -}), $.when.apply(this, b); -}, function(a, b, c) { +}), $.when.apply(this, f); +}, l = $.get(j).then(_.partial(k, j, null), function(a, b, c) { d.push({ data:a, textStatus:b, xhr:c }); -}), l = function() { +}), m = []; +_.each(window.OPENSHIFT_CONFIG.additionalServers, function(a) { +var b = (a.protocol ? a.protocol + "://" :e) + a.hostPort + a.prefix; +m.push($.get(b).then(_.partial(k, b, a), function(a, b, c) { +d.push({ +data:a, +textStatus:b, +xhr:c +}); +})); +}); +var n = function() { window.OPENSHIFT_CONFIG.api.k8s.resources = b.k8s, window.OPENSHIFT_CONFIG.api.openshift.resources = b.openshift, window.OPENSHIFT_CONFIG.apis.groups = c, d.length && (window.OPENSHIFT_CONFIG.apis.API_DISCOVERY_ERRORS = d), a(); -}; -$.when(g, i, k).always(l); +}, o = [ g, i, l ]; +o = o.concat(m), $.when.apply(this, o).always(n); }), window.OPENSHIFT_CONFIG || (window.OPENSHIFT_CONFIG = { apis:{ hostPort:"localhost:8443", @@ -59689,21 +59701,25 @@ error:"API_DISCOVERY" }).toString()); } d = m(d); -var f = d.primaryResource(); +var f, g = d.primaryResource(); if (d.group) { -if (!_.get(b, [ "groups", d.group, "versions", d.version, "resources", f ])) return; +if (f = _.get(b, [ "groups", d.group, "versions", d.version, "resources", g ]), !f) return; +var h = _.get(b, [ "groups", d.group, "hostPrefix" ]) || b; return { -hostPort:b.hostPort, -prefix:b.prefix, +protocol:h.protocol, +hostPort:h.hostPort, +prefix:h.prefix, group:d.group, -version:d.version +version:d.version, +namespaced:f.namespaced }; } -var g; -for (var h in a) if (g = a[h], _.get(g, [ "resources", d.version, f ])) return { -hostPort:g.hostPort, -prefix:g.prefix, -version:d.version +var j; +for (var k in a) if (j = a[k], f = _.get(j, [ "resources", d.version, g ])) return { +hostPort:j.hostPort, +prefix:j.prefix, +version:d.version, +namespaced:f.namespaced }; }, r = function(a) { var b = "", c = ""; @@ -60440,11 +60456,11 @@ o.prototype._urlForResource = function(a, b, c, d, e) { var f = g.apiInfo(a); if (!f) return i.error("_urlForResource called with unknown resource", a, arguments), null; var h; -e = e || {}, h = d ? "http:" === window.location.protocol ? "ws" :"wss" :"http:" === window.location.protocol ? "http" :"https", c && c.namespace && !e.namespace && (e.namespace = c.namespace); -var j = e.namespace, k = null; +if (e = e || {}, h = d ? "http:" === window.location.protocol ? "ws" :"wss" :"http:" === window.location.protocol ? "http" :"https", c && c.namespace && !e.namespace && (e.namespace = c.namespace), f.namespaced && !e.namespace) return i.error("_urlForResource called for a namespaced resource but no namespace provided", a, arguments), null; +var j = f.namespaced, k = null; j && (k = e.namespace, e = angular.copy(e), delete e.namespace); var l, m = { -protocol:h, +protocol:f.protocol || h, hostPort:f.hostPort, prefix:f.prefix, group:f.group,