Blob Blame History Raw
From 9b1e4abb084b6071b90b67a3f6a3ae227b5b3e08 Mon Sep 17 00:00:00 2001
From: petervo <petervo@redhat.com>
Date: Mon, 30 Oct 2017 15:14:07 -0700
Subject: [PATCH 4/6] kubernetes: Updates to work with 3.7

Cherry-pick two commits from master to work with OpenShift 3.7:

  - kubernetes: Use RBAC apiGroup when policybinds is not available
    (373e131e01e6)
  - kubernetes: Use forced to plain-text data in error messages
    (c45a335f59ad)

Also cherry-pick "kubernetes: Fix kubeLoader $destroy callback"
(c19604b9cb) as it's a nice and safe bug fix.
---
 pkg/kubernetes/scripts/kube-client-cockpit.js |   8 +-
 pkg/kubernetes/scripts/kube-client.js         |   2 +-
 pkg/kubernetes/scripts/policy.js              | 104 ++++++++++++++++++++------
 pkg/kubernetes/scripts/projects.js            |  35 +++------
 pkg/kubernetes/scripts/registry.js            |   4 +-
 5 files changed, 102 insertions(+), 51 deletions(-)

diff --git a/pkg/kubernetes/scripts/kube-client-cockpit.js b/pkg/kubernetes/scripts/kube-client-cockpit.js
index 733326c..53ffd6f 100644
--- a/pkg/kubernetes/scripts/kube-client-cockpit.js
+++ b/pkg/kubernetes/scripts/kube-client-cockpit.js
@@ -38,7 +38,13 @@
         try {
             obj = JSON.parse(response.data);
         } catch(e) {
-            return;
+            // Some kubernetes versions message up json reponses
+            if (response.data && response.headers &&
+                response.headers["Content-Type"] === "text/plain") {
+                obj = { message: response.data };
+            } else {
+                return;
+            }
         }
 
         if (obj && obj.message)
diff --git a/pkg/kubernetes/scripts/kube-client.js b/pkg/kubernetes/scripts/kube-client.js
index c2bae9b..39bb1cb 100644
--- a/pkg/kubernetes/scripts/kube-client.js
+++ b/pkg/kubernetes/scripts/kube-client.js
@@ -654,7 +654,7 @@
             function connectUntil(ret, until) {
                 if (until) {
                     if (until.$on) {
-                        until.$on("destroy", function() {
+                        until.$on("$destroy", function() {
                             ret.cancel();
                         });
                     } else {
diff --git a/pkg/kubernetes/scripts/policy.js b/pkg/kubernetes/scripts/policy.js
index 6ce3d23..074cdd1 100644
--- a/pkg/kubernetes/scripts/policy.js
+++ b/pkg/kubernetes/scripts/policy.js
@@ -32,13 +32,51 @@
         '$rootScope',
         'kubeLoader',
         'kubeMethods',
-        function($q, $rootScope, loader, methods) {
+        'kubeSelect',
+        'KubeWatch',
+        'KubeRequest',
+        'KUBE_SCHEMA',
+        function($q, $rootScope, loader, methods, select, watch, KubeRequest, KUBE_SCHEMA) {
+
+            var apiGroup;
+            var RBAC_GROUP = "rbac.authorization.k8s.io";
+            var RBAC_API = "/apis/rbac.authorization.k8s.io/v1beta1";
+            var POLICY_BINDING_API = KUBE_SCHEMA["RoleBinding"]["api"];
+            var watchPromise;
+
+            function setupRoleBinding(group) {
+                KUBE_SCHEMA["RoleBinding"]["api"] = group ? RBAC_API : POLICY_BINDING_API;
+                KUBE_SCHEMA["rolebindings"]["api"] = group ? RBAC_API : POLICY_BINDING_API;
+                apiGroup = group;
+                expireSAR(null);
+                expireWhoCan(null);
+                return group ? "rolebindings" : "policybindings";
+            }
+
+            function ensureWatchType() {
+                if (!watchPromise) {
+                    watchPromise = new KubeRequest("GET", "/oapi/v1")
+                        .then(function(response) {
+                            var data = response.data || {};
+                            var i, l = data.resources || [];
+                            for(i = 0; i < l.length; i++ ) {
+                                if (l[i].kind == "PolicyBinding")
+                                    return setupRoleBinding();
+                            }
+                            return setupRoleBinding(RBAC_GROUP);
+                        }, function(err) {
+                            console.warn("Error getting API", err);
+                            return setupRoleBinding();
+                        });
+                }
+                return watchPromise;
+            }
 
             /*
              * Data loading hacks:
              *
-             * We would like to watch rolebindings, but sadly that's not supported
-             * by origin. So we have to watch policybindings and then infer the
+             * We would like to watch rolebindings, but not all versions support
+             * that. So we have to watch policybindings and then infer the
              * rolebindings from there.
              *
              * In addition we would like to be able to load User and Group objects,
@@ -47,6 +85,15 @@
              */
             loader.listen(function(present, removed) {
                 var link, expire = { };
+
+                /* If reseting clear status */
+                if (!present && !removed) {
+                    expireSAR(null);
+                    expireWhoCan(null);
+                    watchPromise = null;
+                    return;
+                }
+
                 for (link in removed) {
                     if (removed[link].kind == "PolicyBinding") {
                         update_rolebindings(removed[link].roleBindings, true);
@@ -82,6 +129,10 @@
                     if (link in loader.objects)
                         return;
 
+                    /* Don't show system groups */
+                    if (subject.kind == "Group" && subject.name.indexOf("system:") === 0)
+                        return;
+
                     /* An interim object, until perhaps the real thing can be loaded */
                     var interim = { kind: subject.kind, apiVersion: "v1", metadata: { name: subject.name } };
                     if (subject.namespace)
@@ -236,7 +287,6 @@
                 var name = toName(role);
                 var binding = {
                     kind: "RoleBinding",
-                    apiVersion: "v1",
                     metadata: {
                         name: name,
                         namespace: namespace,
@@ -246,7 +296,8 @@
                     groupNames: [],
                     subjects: [],
                     roleRef: {
-                        name: role
+                        name: role,
+                        kind: "ClusterRole",
                     }
                 };
                 addToArray(roleArray(binding, "subjects"), subjects);
@@ -255,6 +306,7 @@
             }
 
             function removeFromRole(project, role, subject) {
+                subject.apiGroup = apiGroup;
                 var namespace = toName(project);
                 return modifyRole(namespace, role, function(data) {
                     removeFromArray(roleArray(data, "subjects"), subject);
@@ -268,26 +320,26 @@
                 });
             }
 
-            function removeMemberFromPolicyBinding(policyBinding, project, subjectRoleBindings, subject) {
+            function removeMemberFromProject(project, subjectRoleBindings, subject) {
                 var registryRoles = ["registry-admin", "registry-editor", "registry-viewer"];
                 var chain = $q.when();
-                var defaultPolicybinding;
                 var roleBindings = [];
+                var defaultPolicybinding = select().kind("PolicyBinding")
+                                            .namespace(project)
+                                            .name(":default").one();
+                subject.apiGroup = apiGroup;
 
-                if(policyBinding && policyBinding.one()){
-                    defaultPolicybinding = policyBinding.one();
+                if(defaultPolicybinding)
                     roleBindings = defaultPolicybinding.roleBindings;
-                }
-                angular.forEach(subjectRoleBindings, function(o) {
-                    angular.forEach(roleBindings, function(role) {
-                        //Since we only added registry roles
-                        //remove ONLY registry roles
-                        if (( indexOf(registryRoles, role.name) !== -1) && role.name === o.metadata.name) {
-                            chain = chain.then(function() {
-                                return removeFromRole(project, role.name, subject);
-                            });
-                        }
-                    });
+
+                angular.forEach(subjectRoleBindings, function(role) {
+                    //Since we only added registry roles
+                    //remove ONLY registry roles
+                    if (indexOf(registryRoles, role.roleRef.name) !== -1) {
+                        chain = chain.then(function() {
+                            return removeFromRole(project, role.roleRef.name, subject);
+                        });
+                    }
                 });
                 return chain;
             }
@@ -334,14 +386,18 @@
 
             return {
                 watch: function watch(until) {
-                    loader.watch("policybindings", until).then(function() {
-                        expireWhoCan(null);
+                    ensureWatchType().then(function (what) {
+                        loader.watch(what, until)
+                            .then(function() {
+                                expireWhoCan(null);
+                            });
                     });
                 },
                 whoCan: function whoCan(project, verb, resource) {
                     return lookupWhoCan(toName(project), verb, resource);
                 },
                 addToRole: function addToRole(project, role, subject) {
+                    subject.apiGroup = apiGroup;
                     var namespace = toName(project);
                     return modifyRole(namespace, role, function(data) {
                         addToArray(roleArray(data, "subjects"), subject);
@@ -356,8 +412,8 @@
                     });
                 },
                 removeFromRole: removeFromRole,
-                removeMemberFromPolicyBinding: removeMemberFromPolicyBinding,
-                subjectAccessReview: subjectAccessReview,
+                removeMemberFromProject: removeMemberFromProject,
+                subjectAccessReview: subjectAccessReview
             };
         }
     ]);
diff --git a/pkg/kubernetes/scripts/projects.js b/pkg/kubernetes/scripts/projects.js
index c616223..1067162 100644
--- a/pkg/kubernetes/scripts/projects.js
+++ b/pkg/kubernetes/scripts/projects.js
@@ -106,10 +106,11 @@
         'projectActions',
         'ListingState',
         'roleActions',
-        function($scope, $routeParams, $location, select, loader, projectData, projectAction, ListingState, roleAction) {
+        'projectPolicy',
+        function($scope, $routeParams, $location, select, loader, projectData, projectAction, ListingState, roleAction, projectPolicy) {
             loader.watch("users", $scope);
             loader.watch("groups", $scope);
-            loader.watch("policybindings", $scope);
+            projectPolicy.watch($scope);
             var namespace = $routeParams["namespace"] || "";
             $scope.projName = namespace;
             if (namespace) {
@@ -149,9 +150,11 @@
         'projectActions',
         'roleActions',
         'ListingState',
-        function($scope, $routeParams, $location, select, loader, projectData, projectAction, roleActions, ListingState) {
+        'projectPolicy',
+        function($scope, $routeParams, $location, select, loader, projectData, projectAction, roleActions, ListingState, projectPolicy) {
             loader.watch("users", $scope);
             loader.watch("groups", $scope);
+            projectPolicy.watch($scope);
             var user = $routeParams["user"] || "";
             $scope.userName = user;
             if (user) {
@@ -192,17 +195,14 @@
         'projectActions',
         'roleActions',
         'ListingState',
-        function($scope, $routeParams, $location, select, loader, projectData, projectAction, roleActions, ListingState) {
+        'projectPolicy',
+        function($scope, $routeParams, $location, select, loader, projectData, projectAction, roleActions, ListingState, projectPolicy) {
             loader.watch("users", $scope);
             loader.watch("groups", $scope);
+            projectPolicy.watch($scope);
             var group = $routeParams["group"] || "";
             $scope.groupName = group;
             if (group) {
-                var groupObj = select().kind("Group").name(group);
-                if (!groupObj || groupObj.length < 1) {
-                     $scope.group = null;
-                    return;
-                }
                 $scope.group = function() {
                     return select().kind("Group").name(group).one();
                 };
@@ -1154,9 +1154,6 @@
         'memberActions',
         "fields",
         function($q, $scope, kselect, loader, methods, projectData, projectPolicy, $location, memberActions, fields) {
-            function getPolicyBinding(namespace){
-                return kselect().kind("PolicyBinding").namespace(namespace).name(":default");
-            }
             function getMembers() {
                 var members = [];
                 var groups = getGroups();
@@ -1228,7 +1225,6 @@
             };
             function removeMemberFromParents(member) {
                 var chain = $q.when();
-                var policyBinding;
                 var groups = projectData.getGroupsWithMember(getGroups(), member.metadata.name);
                 angular.forEach(groups, function(g) {
                     chain = chain.then(function() {
@@ -1237,14 +1233,13 @@
                 });
                 var projects = projectData.getProjectsWithMember(getProjects(), member.metadata.name);
                 angular.forEach(projects, function(project) {
-                    policyBinding = getPolicyBinding(project.metadata.name);
                     var subjectRoleBindings = projectData.subjectRoleBindings(member.metadata.name, project.metadata.name);
                     var subject = {
                         kind: member.kind,
                         name: member.metadata.name,
                     };
                     chain = chain.then(function() {
-                        return projectPolicy.removeMemberFromPolicyBinding(policyBinding,
+                        return projectPolicy.removeMemberFromProject(
                             project.metadata.name, subjectRoleBindings, subject);
                     });
                 });
@@ -1277,13 +1272,12 @@
                     //Project
                     var member = $scope.fields.memberObj;
                     var project = $scope.fields.parentObj.metadata.name;
-                    var policyBinding = getPolicyBinding(project);
                     var subjectRoleBindings = projectData.subjectRoleBindings(member.metadata.name, project);
                     var subject = {
                         kind: member.kind,
                         name: member.metadata.name,
                     };
-                    return projectPolicy.removeMemberFromPolicyBinding(policyBinding, project, subjectRoleBindings, subject);
+                    return projectPolicy.removeMemberFromProject(project, subjectRoleBindings, subject);
                 }
             };
 
@@ -1307,9 +1301,6 @@
             function getProjects() {
                 return kselect().kind("Project");
             }
-            function getPolicyBinding(namespace){
-                return kselect().kind("PolicyBinding").namespace(namespace).name(":default");
-            }
             $scope.select = {
                 member: 'Select Member',
                 members: getUsers(),
@@ -1319,17 +1310,15 @@
             $scope.fields.grpProjects = projectData.getProjectsWithMember(getProjects(), fields.group.metadata.name);
             function removeMemberFromParents(member) {
                 var chain = $q.when();
-                var policyBinding;
                 var projects = projectData.getProjectsWithMember(getProjects(), member.metadata.name);
                 angular.forEach(projects, function(project) {
-                    policyBinding = getPolicyBinding(project.metadata.name);
                     var subjectRoleBindings = projectData.subjectRoleBindings(member.metadata.name, project.metadata.name);
                     var subject = {
                         kind: member.kind,
                         name: member.metadata.name,
                     };
                     chain = chain.then(function() {
-                        return projectPolicy.removeMemberFromPolicyBinding(policyBinding,
+                        return projectPolicy.removeMemberFromProject(
                             project.metadata.name, subjectRoleBindings, subject);
                     });
                 });
diff --git a/pkg/kubernetes/scripts/registry.js b/pkg/kubernetes/scripts/registry.js
index c99cf16..780cc98 100644
--- a/pkg/kubernetes/scripts/registry.js
+++ b/pkg/kubernetes/scripts/registry.js
@@ -105,8 +105,8 @@
         'filterService',
         function($scope, loader, select, discoverSettings, imageData, imageActions, projectActions, projectData, projectPolicy, filter) {
             loader.load("projects");
-            /* Watch the policybindings for project access changes */
-            loader.watch("policybindings", $scope);
+            /* Watch the for project access changes */
+            projectPolicy.watch($scope);
 
             /*
              * For now the dashboard  has to watch all images in
-- 
2.14.3