Browse code

cluster policy commands

deads2k authored on 2015/05/05 04:43:30
Showing 3 changed files
... ...
@@ -10,7 +10,6 @@ import (
10 10
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
11 11
 
12 12
 	authorizationapi "github.com/openshift/origin/pkg/authorization/api"
13
-	"github.com/openshift/origin/pkg/client"
14 13
 	"github.com/openshift/origin/pkg/cmd/util/clientcmd"
15 14
 )
16 15
 
... ...
@@ -19,13 +18,17 @@ const (
19 19
 	AddRoleToUserRecommendedName       = "add-role-to-user"
20 20
 	RemoveRoleFromGroupRecommendedName = "remove-role-from-group"
21 21
 	RemoveRoleFromUserRecommendedName  = "remove-role-from-user"
22
+
23
+	AddClusterRoleToGroupRecommendedName      = "add-cluster-role-to-group"
24
+	AddClusterRoleToUserRecommendedName       = "add-cluster-role-to-user"
25
+	RemoveClusterRoleFromGroupRecommendedName = "remove-cluster-role-from-group"
26
+	RemoveClusterRoleFromUserRecommendedName  = "remove-cluster-role-from-user"
22 27
 )
23 28
 
24 29
 type RoleModificationOptions struct {
25
-	RoleNamespace    string
26
-	RoleName         string
27
-	BindingNamespace string
28
-	Client           client.Interface
30
+	RoleNamespace       string
31
+	RoleName            string
32
+	RoleBindingAccessor RoleBindingAccessor
29 33
 
30 34
 	Users  []string
31 35
 	Groups []string
... ...
@@ -39,7 +42,7 @@ func NewCmdAddRoleToGroup(name, fullName string, f *clientcmd.Factory, out io.Wr
39 39
 		Short: "Add groups to a role in the current project",
40 40
 		Long:  `Add groups to a role in the current project`,
41 41
 		Run: func(cmd *cobra.Command, args []string) {
42
-			if err := options.Complete(f, args, &options.Groups, "group"); err != nil {
42
+			if err := options.Complete(f, args, &options.Groups, "group", true); err != nil {
43 43
 				kcmdutil.CheckErr(kcmdutil.UsageError(cmd, err.Error()))
44 44
 			}
45 45
 
... ...
@@ -49,7 +52,7 @@ func NewCmdAddRoleToGroup(name, fullName string, f *clientcmd.Factory, out io.Wr
49 49
 		},
50 50
 	}
51 51
 
52
-	cmd.Flags().StringVar(&options.RoleNamespace, "role-namespace", "", "namespace where the role is located.")
52
+	cmd.Flags().StringVar(&options.RoleNamespace, "role-namespace", "", "namespace where the role is located: empty means 'ClusterRole'")
53 53
 
54 54
 	return cmd
55 55
 }
... ...
@@ -62,7 +65,7 @@ func NewCmdAddRoleToUser(name, fullName string, f *clientcmd.Factory, out io.Wri
62 62
 		Short: "Add users to a role in the current project",
63 63
 		Long:  `Add users to a role in the current project`,
64 64
 		Run: func(cmd *cobra.Command, args []string) {
65
-			if err := options.Complete(f, args, &options.Users, "user"); err != nil {
65
+			if err := options.Complete(f, args, &options.Users, "user", true); err != nil {
66 66
 				kcmdutil.CheckErr(kcmdutil.UsageError(cmd, err.Error()))
67 67
 			}
68 68
 
... ...
@@ -72,7 +75,7 @@ func NewCmdAddRoleToUser(name, fullName string, f *clientcmd.Factory, out io.Wri
72 72
 		},
73 73
 	}
74 74
 
75
-	cmd.Flags().StringVar(&options.RoleNamespace, "role-namespace", "", "namespace where the role is located.")
75
+	cmd.Flags().StringVar(&options.RoleNamespace, "role-namespace", "", "namespace where the role is located: empty means 'ClusterRole'")
76 76
 
77 77
 	return cmd
78 78
 }
... ...
@@ -85,7 +88,7 @@ func NewCmdRemoveRoleFromGroup(name, fullName string, f *clientcmd.Factory, out
85 85
 		Short: "Remove group from role in the current project",
86 86
 		Long:  `Remove group from role in the current project`,
87 87
 		Run: func(cmd *cobra.Command, args []string) {
88
-			if err := options.Complete(f, args, &options.Groups, "group"); err != nil {
88
+			if err := options.Complete(f, args, &options.Groups, "group", true); err != nil {
89 89
 				kcmdutil.CheckErr(kcmdutil.UsageError(cmd, err.Error()))
90 90
 			}
91 91
 
... ...
@@ -95,7 +98,7 @@ func NewCmdRemoveRoleFromGroup(name, fullName string, f *clientcmd.Factory, out
95 95
 		},
96 96
 	}
97 97
 
98
-	cmd.Flags().StringVar(&options.RoleNamespace, "role-namespace", "", "namespace where the role is located.")
98
+	cmd.Flags().StringVar(&options.RoleNamespace, "role-namespace", "", "namespace where the role is located: empty means 'ClusterRole'")
99 99
 
100 100
 	return cmd
101 101
 }
... ...
@@ -108,7 +111,72 @@ func NewCmdRemoveRoleFromUser(name, fullName string, f *clientcmd.Factory, out i
108 108
 		Short: "Remove user from role in the current project",
109 109
 		Long:  `Remove user from role in the current project`,
110 110
 		Run: func(cmd *cobra.Command, args []string) {
111
-			if err := options.Complete(f, args, &options.Users, "user"); err != nil {
111
+			if err := options.Complete(f, args, &options.Users, "user", true); err != nil {
112
+				kcmdutil.CheckErr(kcmdutil.UsageError(cmd, err.Error()))
113
+			}
114
+
115
+			if err := options.RemoveRole(); err != nil {
116
+				kcmdutil.CheckErr(err)
117
+			}
118
+		},
119
+	}
120
+
121
+	cmd.Flags().StringVar(&options.RoleNamespace, "role-namespace", "", "namespace where the role is located: empty means 'ClusterRole'")
122
+
123
+	return cmd
124
+}
125
+
126
+func NewCmdAddClusterRoleToGroup(name, fullName string, f *clientcmd.Factory, out io.Writer) *cobra.Command {
127
+	options := &RoleModificationOptions{}
128
+
129
+	cmd := &cobra.Command{
130
+		Use:   name + " <role> <group> [group]...",
131
+		Short: "add groups to a role for all projects in the cluster",
132
+		Long:  `add groups to a role for all projects in the cluster`,
133
+		Run: func(cmd *cobra.Command, args []string) {
134
+			if err := options.Complete(f, args, &options.Groups, "group", false); err != nil {
135
+				kcmdutil.CheckErr(kcmdutil.UsageError(cmd, err.Error()))
136
+			}
137
+
138
+			if err := options.AddRole(); err != nil {
139
+				kcmdutil.CheckErr(err)
140
+			}
141
+		},
142
+	}
143
+
144
+	return cmd
145
+}
146
+
147
+func NewCmdAddClusterRoleToUser(name, fullName string, f *clientcmd.Factory, out io.Writer) *cobra.Command {
148
+	options := &RoleModificationOptions{}
149
+
150
+	cmd := &cobra.Command{
151
+		Use:   name + " <role> <user> [user]...",
152
+		Short: "add users to a role for all projects in the cluster",
153
+		Long:  `add users to a role for all projects in the cluster`,
154
+		Run: func(cmd *cobra.Command, args []string) {
155
+			if err := options.Complete(f, args, &options.Users, "user", false); err != nil {
156
+				kcmdutil.CheckErr(kcmdutil.UsageError(cmd, err.Error()))
157
+			}
158
+
159
+			if err := options.AddRole(); err != nil {
160
+				kcmdutil.CheckErr(err)
161
+			}
162
+		},
163
+	}
164
+
165
+	return cmd
166
+}
167
+
168
+func NewCmdRemoveClusterRoleFromGroup(name, fullName string, f *clientcmd.Factory, out io.Writer) *cobra.Command {
169
+	options := &RoleModificationOptions{}
170
+
171
+	cmd := &cobra.Command{
172
+		Use:   name + " <role> <group> [group]...",
173
+		Short: "remove group from role for all projects in the cluster",
174
+		Long:  `remove group from role for all projects in the cluster`,
175
+		Run: func(cmd *cobra.Command, args []string) {
176
+			if err := options.Complete(f, args, &options.Groups, "group", false); err != nil {
112 177
 				kcmdutil.CheckErr(kcmdutil.UsageError(cmd, err.Error()))
113 178
 			}
114 179
 
... ...
@@ -118,12 +186,31 @@ func NewCmdRemoveRoleFromUser(name, fullName string, f *clientcmd.Factory, out i
118 118
 		},
119 119
 	}
120 120
 
121
-	cmd.Flags().StringVar(&options.RoleNamespace, "role-namespace", "", "namespace where the role is located.")
121
+	return cmd
122
+}
123
+
124
+func NewCmdRemoveClusterRoleFromUser(name, fullName string, f *clientcmd.Factory, out io.Writer) *cobra.Command {
125
+	options := &RoleModificationOptions{}
126
+
127
+	cmd := &cobra.Command{
128
+		Use:   name + " <role> <user> [user]...",
129
+		Short: "remove user from role for all projects in the cluster",
130
+		Long:  `remove user from role for all projects in the cluster`,
131
+		Run: func(cmd *cobra.Command, args []string) {
132
+			if err := options.Complete(f, args, &options.Users, "user", false); err != nil {
133
+				kcmdutil.CheckErr(kcmdutil.UsageError(cmd, err.Error()))
134
+			}
135
+
136
+			if err := options.RemoveRole(); err != nil {
137
+				kcmdutil.CheckErr(err)
138
+			}
139
+		},
140
+	}
122 141
 
123 142
 	return cmd
124 143
 }
125 144
 
126
-func (o *RoleModificationOptions) Complete(f *clientcmd.Factory, args []string, target *[]string, targetName string) error {
145
+func (o *RoleModificationOptions) Complete(f *clientcmd.Factory, args []string, target *[]string, targetName string, isNamespaced bool) error {
127 146
 	if len(args) < 2 {
128 147
 		return fmt.Errorf("You must specify at least two arguments: <role> <%s> [%s]...", targetName, targetName)
129 148
 	}
... ...
@@ -131,23 +218,32 @@ func (o *RoleModificationOptions) Complete(f *clientcmd.Factory, args []string,
131 131
 	o.RoleName = args[0]
132 132
 	*target = append(*target, args[1:]...)
133 133
 
134
-	var err error
135
-	if o.Client, _, err = f.Clients(); err != nil {
134
+	osClient, _, err := f.Clients()
135
+	if err != nil {
136 136
 		return err
137 137
 	}
138
-	if o.BindingNamespace, err = f.DefaultNamespace(); err != nil {
139
-		return err
138
+
139
+	if isNamespaced {
140
+		roleBindingNamespace, err := f.DefaultNamespace()
141
+		if err != nil {
142
+			return err
143
+		}
144
+		o.RoleBindingAccessor = NewLocalRoleBindingAccessor(roleBindingNamespace, osClient)
145
+
146
+	} else {
147
+		o.RoleBindingAccessor = NewClusterRoleBindingAccessor(osClient)
148
+
140 149
 	}
141 150
 
142 151
 	return nil
143 152
 }
144 153
 
145 154
 func (o *RoleModificationOptions) AddRole() error {
146
-	roleBindings, err := getExistingRoleBindingsForRole(o.RoleNamespace, o.RoleName, o.Client.PolicyBindings(o.BindingNamespace))
155
+	roleBindings, err := o.RoleBindingAccessor.GetExistingRoleBindingsForRole(o.RoleNamespace, o.RoleName)
147 156
 	if err != nil {
148 157
 		return err
149 158
 	}
150
-	roleBindingNames, err := getExistingRoleBindingNames(o.Client.PolicyBindings(o.BindingNamespace))
159
+	roleBindingNames, err := o.RoleBindingAccessor.GetExistingRoleBindingNames()
151 160
 	if err != nil {
152 161
 		return err
153 162
 	}
... ...
@@ -169,10 +265,10 @@ func (o *RoleModificationOptions) AddRole() error {
169 169
 	roleBinding.Groups.Insert(o.Groups...)
170 170
 
171 171
 	if isUpdate {
172
-		_, err = o.Client.RoleBindings(o.BindingNamespace).Update(roleBinding)
172
+		err = o.RoleBindingAccessor.UpdateRoleBinding(roleBinding)
173 173
 	} else {
174 174
 		roleBinding.Name = getUniqueName(o.RoleName, roleBindingNames)
175
-		_, err = o.Client.RoleBindings(o.BindingNamespace).Create(roleBinding)
175
+		err = o.RoleBindingAccessor.CreateRoleBinding(roleBinding)
176 176
 	}
177 177
 	if err != nil {
178 178
 		return err
... ...
@@ -182,19 +278,19 @@ func (o *RoleModificationOptions) AddRole() error {
182 182
 }
183 183
 
184 184
 func (o *RoleModificationOptions) RemoveRole() error {
185
-	roleBindings, err := getExistingRoleBindingsForRole(o.RoleNamespace, o.RoleName, o.Client.PolicyBindings(o.BindingNamespace))
185
+	roleBindings, err := o.RoleBindingAccessor.GetExistingRoleBindingsForRole(o.RoleNamespace, o.RoleName)
186 186
 	if err != nil {
187 187
 		return err
188 188
 	}
189 189
 	if len(roleBindings) == 0 {
190
-		return fmt.Errorf("unable to locate RoleBinding for %v::%v in %v", o.RoleNamespace, o.RoleName, o.BindingNamespace)
190
+		return fmt.Errorf("unable to locate RoleBinding for %v/%v", o.RoleNamespace, o.RoleName)
191 191
 	}
192 192
 
193 193
 	for _, roleBinding := range roleBindings {
194 194
 		roleBinding.Groups.Delete(o.Groups...)
195 195
 		roleBinding.Users.Delete(o.Users...)
196 196
 
197
-		_, err = o.Client.RoleBindings(o.BindingNamespace).Update(roleBinding)
197
+		err = o.RoleBindingAccessor.UpdateRoleBinding(roleBinding)
198 198
 		if err != nil {
199 199
 			return err
200 200
 		}
... ...
@@ -3,8 +3,8 @@ package policy
3 3
 import (
4 4
 	"fmt"
5 5
 	"io"
6
-	"strings"
7 6
 
7
+	kapierrors "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
8 8
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
9 9
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
10 10
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
... ...
@@ -28,13 +28,19 @@ func NewCommandPolicy(name, fullName string, f *clientcmd.Factory, out io.Writer
28 28
 		Run:   runHelp,
29 29
 	}
30 30
 
31
+	cmds.AddCommand(NewCmdWhoCan(WhoCanRecommendedName, fullName+" "+WhoCanRecommendedName, f, out))
32
+
31 33
 	cmds.AddCommand(NewCmdAddRoleToUser(AddRoleToUserRecommendedName, fullName+" "+AddRoleToUserRecommendedName, f, out))
32 34
 	cmds.AddCommand(NewCmdRemoveRoleFromUser(RemoveRoleFromUserRecommendedName, fullName+" "+RemoveRoleFromUserRecommendedName, f, out))
33 35
 	cmds.AddCommand(NewCmdRemoveUserFromProject(RemoveUserRecommendedName, fullName+" "+RemoveUserRecommendedName, f, out))
34 36
 	cmds.AddCommand(NewCmdAddRoleToGroup(AddRoleToGroupRecommendedName, fullName+" "+AddRoleToGroupRecommendedName, f, out))
35 37
 	cmds.AddCommand(NewCmdRemoveRoleFromGroup(RemoveRoleFromGroupRecommendedName, fullName+" "+RemoveRoleFromGroupRecommendedName, f, out))
36 38
 	cmds.AddCommand(NewCmdRemoveGroupFromProject(RemoveGroupRecommendedName, fullName+" "+RemoveGroupRecommendedName, f, out))
37
-	cmds.AddCommand(NewCmdWhoCan(WhoCanRecommendedName, fullName+" "+WhoCanRecommendedName, f, out))
39
+
40
+	cmds.AddCommand(NewCmdAddClusterRoleToUser(AddClusterRoleToUserRecommendedName, fullName+" "+AddClusterRoleToUserRecommendedName, f, out))
41
+	cmds.AddCommand(NewCmdRemoveClusterRoleFromUser(RemoveClusterRoleFromUserRecommendedName, fullName+" "+RemoveClusterRoleFromUserRecommendedName, f, out))
42
+	cmds.AddCommand(NewCmdAddClusterRoleToGroup(AddClusterRoleToGroupRecommendedName, fullName+" "+AddClusterRoleToGroupRecommendedName, f, out))
43
+	cmds.AddCommand(NewCmdRemoveClusterRoleFromGroup(RemoveClusterRoleFromGroupRecommendedName, fullName+" "+RemoveClusterRoleFromGroupRecommendedName, f, out))
38 44
 
39 45
 	return cmds
40 46
 }
... ...
@@ -66,9 +72,27 @@ func getUniqueName(basename string, existingNames *util.StringSet) string {
66 66
 	return string(util.NewUUID())
67 67
 }
68 68
 
69
-func getExistingRoleBindingsForRole(roleNamespace, role string, bindingInterface client.PolicyBindingInterface) ([]*authorizationapi.RoleBinding, error) {
70
-	existingBindings, err := bindingInterface.Get(authorizationapi.GetPolicyBindingName(roleNamespace))
71
-	if err != nil && !strings.Contains(err.Error(), " not found") {
69
+// RoleBindingAccessor is used by role modification commands to access and modify roles
70
+type RoleBindingAccessor interface {
71
+	GetExistingRoleBindingsForRole(roleNamespace, role string) ([]*authorizationapi.RoleBinding, error)
72
+	GetExistingRoleBindingNames() (*util.StringSet, error)
73
+	UpdateRoleBinding(binding *authorizationapi.RoleBinding) error
74
+	CreateRoleBinding(binding *authorizationapi.RoleBinding) error
75
+}
76
+
77
+// LocalRoleBindingAccessor operates against role bindings in namespace
78
+type LocalRoleBindingAccessor struct {
79
+	BindingNamespace string
80
+	Client           client.Interface
81
+}
82
+
83
+func NewLocalRoleBindingAccessor(bindingNamespace string, client client.Interface) LocalRoleBindingAccessor {
84
+	return LocalRoleBindingAccessor{bindingNamespace, client}
85
+}
86
+
87
+func (a LocalRoleBindingAccessor) GetExistingRoleBindingsForRole(roleNamespace, role string) ([]*authorizationapi.RoleBinding, error) {
88
+	existingBindings, err := a.Client.PolicyBindings(a.BindingNamespace).Get(authorizationapi.GetPolicyBindingName(roleNamespace))
89
+	if err != nil && !kapierrors.IsNotFound(err) {
72 90
 		return nil, err
73 91
 	}
74 92
 
... ...
@@ -84,8 +108,8 @@ func getExistingRoleBindingsForRole(roleNamespace, role string, bindingInterface
84 84
 	return ret, nil
85 85
 }
86 86
 
87
-func getExistingRoleBindingNames(bindingInterface client.PolicyBindingInterface) (*util.StringSet, error) {
88
-	policyBindings, err := bindingInterface.List(labels.Everything(), fields.Everything())
87
+func (a LocalRoleBindingAccessor) GetExistingRoleBindingNames() (*util.StringSet, error) {
88
+	policyBindings, err := a.Client.PolicyBindings(a.BindingNamespace).List(labels.Everything(), fields.Everything())
89 89
 	if err != nil {
90 90
 		return nil, err
91 91
 	}
... ...
@@ -99,3 +123,72 @@ func getExistingRoleBindingNames(bindingInterface client.PolicyBindingInterface)
99 99
 
100 100
 	return ret, nil
101 101
 }
102
+
103
+func (a LocalRoleBindingAccessor) UpdateRoleBinding(binding *authorizationapi.RoleBinding) error {
104
+	_, err := a.Client.RoleBindings(a.BindingNamespace).Update(binding)
105
+	return err
106
+}
107
+
108
+func (a LocalRoleBindingAccessor) CreateRoleBinding(binding *authorizationapi.RoleBinding) error {
109
+	binding.Namespace = a.BindingNamespace
110
+	_, err := a.Client.RoleBindings(a.BindingNamespace).Create(binding)
111
+	return err
112
+}
113
+
114
+// ClusterRoleBindingAccessor operates against cluster scoped role bindings
115
+type ClusterRoleBindingAccessor struct {
116
+	Client client.Interface
117
+}
118
+
119
+func NewClusterRoleBindingAccessor(client client.Interface) ClusterRoleBindingAccessor {
120
+	// the master namespace value doesn't matter because we're round tripping all the values, so the namespace gets stripped out
121
+	return ClusterRoleBindingAccessor{client}
122
+}
123
+
124
+func (a ClusterRoleBindingAccessor) GetExistingRoleBindingsForRole(roleNamespace, role string) ([]*authorizationapi.RoleBinding, error) {
125
+	uncast, err := a.Client.ClusterPolicyBindings().Get(authorizationapi.GetPolicyBindingName(roleNamespace))
126
+	if err != nil && !kapierrors.IsNotFound(err) {
127
+		return nil, err
128
+	}
129
+	existingBindings := authorizationapi.ToPolicyBinding(uncast)
130
+
131
+	ret := make([]*authorizationapi.RoleBinding, 0)
132
+	// see if we can find an existing binding that points to the role in question.
133
+	for _, currBinding := range existingBindings.RoleBindings {
134
+		if currBinding.RoleRef.Name == role {
135
+			t := currBinding
136
+			ret = append(ret, &t)
137
+		}
138
+	}
139
+
140
+	return ret, nil
141
+}
142
+
143
+func (a ClusterRoleBindingAccessor) GetExistingRoleBindingNames() (*util.StringSet, error) {
144
+	uncast, err := a.Client.ClusterPolicyBindings().List(labels.Everything(), fields.Everything())
145
+	if err != nil {
146
+		return nil, err
147
+	}
148
+	policyBindings := authorizationapi.ToPolicyBindingList(uncast)
149
+
150
+	ret := &util.StringSet{}
151
+	for _, existingBindings := range policyBindings.Items {
152
+		for _, currBinding := range existingBindings.RoleBindings {
153
+			ret.Insert(currBinding.Name)
154
+		}
155
+	}
156
+
157
+	return ret, nil
158
+}
159
+
160
+func (a ClusterRoleBindingAccessor) UpdateRoleBinding(binding *authorizationapi.RoleBinding) error {
161
+	clusterBinding := authorizationapi.ToClusterRoleBinding(binding)
162
+	_, err := a.Client.ClusterRoleBindings().Update(clusterBinding)
163
+	return err
164
+}
165
+
166
+func (a ClusterRoleBindingAccessor) CreateRoleBinding(binding *authorizationapi.RoleBinding) error {
167
+	clusterBinding := authorizationapi.ToClusterRoleBinding(binding)
168
+	_, err := a.Client.ClusterRoleBindings().Create(clusterBinding)
169
+	return err
170
+}
... ...
@@ -26,9 +26,8 @@ type NewProjectOptions struct {
26 26
 
27 27
 	Client client.Interface
28 28
 
29
-	AdminRole             string
30
-	MasterPolicyNamespace string
31
-	AdminUser             string
29
+	AdminRole string
30
+	AdminUser string
32 31
 }
33 32
 
34 33
 func NewCmdNewProject(name, fullName string, f *clientcmd.Factory, out io.Writer) *cobra.Command {
... ...
@@ -92,16 +91,14 @@ func (o *NewProjectOptions) Run() error {
92 92
 
93 93
 	if len(o.AdminUser) != 0 {
94 94
 		adduser := &policy.RoleModificationOptions{
95
-			RoleNamespace:    o.MasterPolicyNamespace,
96
-			RoleName:         o.AdminRole,
97
-			BindingNamespace: project.Name,
98
-			Client:           o.Client,
99
-			Users:            []string{o.AdminUser},
95
+			RoleName:            o.AdminRole,
96
+			RoleBindingAccessor: policy.NewLocalRoleBindingAccessor(project.Name, o.Client),
97
+			Users:               []string{o.AdminUser},
100 98
 		}
101 99
 
102 100
 		if err := adduser.AddRole(); err != nil {
103 101
 			fmt.Printf("The project %v was created, but %v could not be added to the %v role.\n", o.ProjectName, o.AdminUser, o.AdminRole)
104
-			fmt.Printf("To add the user to the existing project, run\n\n\topenshift ex policy add-role-to-user --namespace=%v --role-namespace=%v %v %v\n", o.ProjectName, o.MasterPolicyNamespace, o.AdminRole, o.AdminUser)
102
+			fmt.Printf("To add the user to the existing project, run\n\n\topenshift ex policy add-role-to-user --namespace=%v %v %v\n", o.ProjectName, o.AdminRole, o.AdminUser)
105 103
 			return err
106 104
 		}
107 105
 	}