| ... | ... |
@@ -12,9 +12,9 @@ |
| 12 | 12 |
</div> |
| 13 | 13 |
<div ng-if="emptyMessage && (projects | hashSize) == 0">{{emptyMessage}}</div>
|
| 14 | 14 |
<div style="margin-top: 10px;"> |
| 15 |
- To create a new project, run <code>openshift ex new-project <projectname> --admin={{user.metadata.name || '<YourUsername>'}}</code>
|
|
| 15 |
+ To create a new project, run <code>osc new-project <projectname></code> |
|
| 16 | 16 |
</div> |
| 17 | 17 |
<div style="margin-top: 10px;"> |
| 18 |
- To be added as an admin to an existing project, run <code>openshift ex policy add-role-to-user admin {{user.metadata.name || '<YourUsername>'}} -n <projectname></code>
|
|
| 18 |
+ To be added as an admin to an existing project, run <code>osadm policy add-role-to-user admin {{user.metadata.name || '<YourUsername>'}} -n <projectname></code>
|
|
| 19 | 19 |
</div> |
| 20 | 20 |
</div> |
| ... | ... |
@@ -80,7 +80,7 @@ var originTypes = []string{
|
| 80 | 80 |
"Image", "ImageRepository", "ImageStream", "ImageRepositoryMapping", "ImageStreamMapping", "ImageRepositoryTag", "ImageStreamTag", "ImageStreamImage", |
| 81 | 81 |
"Template", "TemplateConfig", "ProcessedTemplate", |
| 82 | 82 |
"Route", |
| 83 |
- "Project", |
|
| 83 |
+ "Project", "ProjectRequest", |
|
| 84 | 84 |
"User", "Identity", "UserIdentityMapping", |
| 85 | 85 |
"OAuthClient", "OAuthClientAuthorization", "OAuthAccessToken", "OAuthAuthorizeToken", |
| 86 | 86 |
"Role", "RoleBinding", "Policy", "PolicyBinding", "ResourceAccessReview", "SubjectAccessReview", |
| ... | ... |
@@ -130,7 +130,8 @@ func init() {
|
| 130 | 130 |
// the list of kinds that are scoped at the root of the api hierarchy |
| 131 | 131 |
// if a kind is not enumerated here, it is assumed to have a namespace scope |
| 132 | 132 |
kindToRootScope := map[string]bool{
|
| 133 |
- "Project": true, |
|
| 133 |
+ "Project": true, |
|
| 134 |
+ "ProjectRequest": true, |
|
| 134 | 135 |
|
| 135 | 136 |
"User": true, |
| 136 | 137 |
"Identity": true, |
| ... | ... |
@@ -62238,10 +62238,10 @@ var _views_projects_html = []byte(`<div class="container"> |
| 62238 | 62238 |
</div> |
| 62239 | 62239 |
<div ng-if="emptyMessage && (projects | hashSize) == 0">{{emptyMessage}}</div>
|
| 62240 | 62240 |
<div style="margin-top: 10px"> |
| 62241 |
-To create a new project, run <code>openshift ex new-project <projectname> --admin={{user.metadata.name || '<YourUsername>'}}</code>
|
|
| 62241 |
+To create a new project, run <code>osc new-project <projectname></code> |
|
| 62242 | 62242 |
</div> |
| 62243 | 62243 |
<div style="margin-top: 10px"> |
| 62244 |
-To be added as an admin to an existing project, run <code>openshift ex policy add-role-to-user admin {{user.metadata.name || '<YourUsername>'}} -n <projectname></code>
|
|
| 62244 |
+To be added as an admin to an existing project, run <code>osadm policy add-role-to-user admin {{user.metadata.name || '<YourUsername>'}} -n <projectname></code>
|
|
| 62245 | 62245 |
</div> |
| 62246 | 62246 |
</div>`) |
| 62247 | 62247 |
|
| ... | ... |
@@ -440,7 +440,7 @@ func newDefaultGlobalBinding() []authorizationapi.PolicyBinding {
|
| 440 | 440 |
Namespace: bootstrappolicy.DefaultMasterAuthorizationNamespace, |
| 441 | 441 |
}, |
| 442 | 442 |
RoleBindings: map[string]authorizationapi.RoleBinding{
|
| 443 |
- "cluster-admins": {
|
|
| 443 |
+ "extra-cluster-admins": {
|
|
| 444 | 444 |
ObjectMeta: kapi.ObjectMeta{
|
| 445 | 445 |
Name: "cluster-admins", |
| 446 | 446 |
Namespace: bootstrappolicy.DefaultMasterAuthorizationNamespace, |
| ... | ... |
@@ -33,6 +33,7 @@ type Interface interface {
|
| 33 | 33 |
UsersInterface |
| 34 | 34 |
UserIdentityMappingsInterface |
| 35 | 35 |
ProjectsInterface |
| 36 |
+ ProjectRequestsInterface |
|
| 36 | 37 |
PoliciesNamespacer |
| 37 | 38 |
RolesNamespacer |
| 38 | 39 |
RoleBindingsNamespacer |
| ... | ... |
@@ -134,6 +135,11 @@ func (c *Client) Projects() ProjectInterface {
|
| 134 | 134 |
return newProjects(c) |
| 135 | 135 |
} |
| 136 | 136 |
|
| 137 |
+// ProjectRequests provides a REST client for Projects |
|
| 138 |
+func (c *Client) ProjectRequests() ProjectRequestInterface {
|
|
| 139 |
+ return newProjectRequests(c) |
|
| 140 |
+} |
|
| 141 |
+ |
|
| 137 | 142 |
// TemplateConfigs provides a REST client for TemplateConfig |
| 138 | 143 |
func (c *Client) TemplateConfigs(namespace string) TemplateConfigInterface {
|
| 139 | 144 |
return newTemplateConfigs(c, namespace) |
| ... | ... |
@@ -108,6 +108,10 @@ func (c *Fake) Projects() ProjectInterface {
|
| 108 | 108 |
return &FakeProjects{Fake: c}
|
| 109 | 109 |
} |
| 110 | 110 |
|
| 111 |
+func (c *Fake) ProjectRequests() ProjectRequestInterface {
|
|
| 112 |
+ return &FakeProjectRequests{Fake: c}
|
|
| 113 |
+} |
|
| 114 |
+ |
|
| 111 | 115 |
func (c *Fake) Policies(namespace string) PolicyInterface {
|
| 112 | 116 |
return &FakePolicies{Fake: c}
|
| 113 | 117 |
} |
| 114 | 118 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,14 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ projectapi "github.com/openshift/origin/pkg/project/api" |
|
| 4 |
+) |
|
| 5 |
+ |
|
| 6 |
+type FakeProjectRequests struct {
|
|
| 7 |
+ Fake *Fake |
|
| 8 |
+} |
|
| 9 |
+ |
|
| 10 |
+func (c *FakeProjectRequests) Create(project *projectapi.ProjectRequest) (*projectapi.Project, error) {
|
|
| 11 |
+ obj, err := c.Fake.Invokes(FakeAction{Action: "create-newProject", Value: project}, &projectapi.ProjectRequest{})
|
|
| 12 |
+ return obj.(*projectapi.Project), err |
|
| 13 |
+} |
| 0 | 14 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,34 @@ |
| 0 |
+package client |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ projectapi "github.com/openshift/origin/pkg/project/api" |
|
| 4 |
+ _ "github.com/openshift/origin/pkg/user/api/v1beta1" |
|
| 5 |
+) |
|
| 6 |
+ |
|
| 7 |
+// UsersInterface has methods to work with User resources in a namespace |
|
| 8 |
+type ProjectRequestsInterface interface {
|
|
| 9 |
+ ProjectRequests() ProjectRequestInterface |
|
| 10 |
+} |
|
| 11 |
+ |
|
| 12 |
+// UserInterface exposes methods on user resources. |
|
| 13 |
+type ProjectRequestInterface interface {
|
|
| 14 |
+ Create(p *projectapi.ProjectRequest) (*projectapi.Project, error) |
|
| 15 |
+} |
|
| 16 |
+ |
|
| 17 |
+type newProjectRequestsStruct struct {
|
|
| 18 |
+ r *Client |
|
| 19 |
+} |
|
| 20 |
+ |
|
| 21 |
+// newUsers returns a users |
|
| 22 |
+func newProjectRequests(c *Client) *newProjectRequestsStruct {
|
|
| 23 |
+ return &newProjectRequestsStruct{
|
|
| 24 |
+ r: c, |
|
| 25 |
+ } |
|
| 26 |
+} |
|
| 27 |
+ |
|
| 28 |
+// Create creates a new ProjectRequest |
|
| 29 |
+func (c *newProjectRequestsStruct) Create(p *projectapi.ProjectRequest) (result *projectapi.Project, err error) {
|
|
| 30 |
+ result = &projectapi.Project{}
|
|
| 31 |
+ err = c.r.Post().Resource("projectrequests").Body(p).Do().Into(result)
|
|
| 32 |
+ return |
|
| 33 |
+} |
| ... | ... |
@@ -64,6 +64,7 @@ func NewCommandCLI(name, fullName string) *cobra.Command {
|
| 64 | 64 |
|
| 65 | 65 |
cmds.AddCommand(cmd.NewCmdLogin(f, in, out)) |
| 66 | 66 |
cmds.AddCommand(cmd.NewCmdProject(f, out)) |
| 67 |
+ cmds.AddCommand(cmd.NewCmdRequestProject("new-project", fullName+" new-project", fullName+" login", fullName+" project", f, out))
|
|
| 67 | 68 |
cmds.AddCommand(cmd.NewCmdNewApplication(fullName, f, out)) |
| 68 | 69 |
cmds.AddCommand(cmd.NewCmdStatus(fullName, f, out)) |
| 69 | 70 |
cmds.AddCommand(cmd.NewCmdStartBuild(fullName, f, out)) |
| 70 | 71 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,93 @@ |
| 0 |
+package cmd |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "io" |
|
| 5 |
+ |
|
| 6 |
+ "github.com/golang/glog" |
|
| 7 |
+ "github.com/spf13/cobra" |
|
| 8 |
+ |
|
| 9 |
+ "github.com/openshift/origin/pkg/client" |
|
| 10 |
+ "github.com/openshift/origin/pkg/cmd/util/clientcmd" |
|
| 11 |
+ projectapi "github.com/openshift/origin/pkg/project/api" |
|
| 12 |
+) |
|
| 13 |
+ |
|
| 14 |
+type NewProjectOptions struct {
|
|
| 15 |
+ ProjectName string |
|
| 16 |
+ DisplayName string |
|
| 17 |
+ Description string |
|
| 18 |
+ |
|
| 19 |
+ Client client.Interface |
|
| 20 |
+} |
|
| 21 |
+ |
|
| 22 |
+const requestProjectLongDesc = ` |
|
| 23 |
+Create a new project for yourself in OpenShift with you as the project admin. |
|
| 24 |
+ |
|
| 25 |
+Assuming your cluster admin has granted you permission, this command will |
|
| 26 |
+create a new project for you and assign you as the project admin. You must |
|
| 27 |
+be logged in, so you might have to run %[2]s first. |
|
| 28 |
+ |
|
| 29 |
+Examples: |
|
| 30 |
+ |
|
| 31 |
+ $ Create a new project with minimal information |
|
| 32 |
+ $ %[1]s web-team-dev |
|
| 33 |
+ |
|
| 34 |
+ # Create a new project with a description |
|
| 35 |
+ $ %[1]s web-team-dev --display-name="Web Team Development" --description="Development project for the web team." |
|
| 36 |
+ |
|
| 37 |
+After your project is created you can switch to it using %[3]s <project name>. |
|
| 38 |
+` |
|
| 39 |
+ |
|
| 40 |
+func NewCmdRequestProject(name, fullName, oscLoginName, oscProjectName string, f *clientcmd.Factory, out io.Writer) *cobra.Command {
|
|
| 41 |
+ options := &NewProjectOptions{}
|
|
| 42 |
+ |
|
| 43 |
+ cmd := &cobra.Command{
|
|
| 44 |
+ Use: fmt.Sprintf("%s <project-name> [--display-name=<your display name> --description=<your description]", name),
|
|
| 45 |
+ Short: "request a new project", |
|
| 46 |
+ Long: fmt.Sprintf(requestProjectLongDesc, fullName, oscLoginName, oscProjectName), |
|
| 47 |
+ Run: func(cmd *cobra.Command, args []string) {
|
|
| 48 |
+ if !options.complete(cmd) {
|
|
| 49 |
+ return |
|
| 50 |
+ } |
|
| 51 |
+ |
|
| 52 |
+ var err error |
|
| 53 |
+ if options.Client, _, err = f.Clients(); err != nil {
|
|
| 54 |
+ glog.Fatalf("Error getting client: %v", err)
|
|
| 55 |
+ } |
|
| 56 |
+ if err := options.Run(); err != nil {
|
|
| 57 |
+ glog.Fatal(err) |
|
| 58 |
+ } |
|
| 59 |
+ }, |
|
| 60 |
+ } |
|
| 61 |
+ cmd.SetOutput(out) |
|
| 62 |
+ |
|
| 63 |
+ cmd.Flags().StringVar(&options.DisplayName, "display-name", "", "project display name") |
|
| 64 |
+ cmd.Flags().StringVar(&options.Description, "description", "", "project description") |
|
| 65 |
+ |
|
| 66 |
+ return cmd |
|
| 67 |
+} |
|
| 68 |
+ |
|
| 69 |
+func (o *NewProjectOptions) complete(cmd *cobra.Command) bool {
|
|
| 70 |
+ args := cmd.Flags().Args() |
|
| 71 |
+ if len(args) != 1 {
|
|
| 72 |
+ cmd.Help() |
|
| 73 |
+ return false |
|
| 74 |
+ } |
|
| 75 |
+ |
|
| 76 |
+ o.ProjectName = args[0] |
|
| 77 |
+ |
|
| 78 |
+ return true |
|
| 79 |
+} |
|
| 80 |
+ |
|
| 81 |
+func (o *NewProjectOptions) Run() error {
|
|
| 82 |
+ projectRequest := &projectapi.ProjectRequest{}
|
|
| 83 |
+ projectRequest.Name = o.ProjectName |
|
| 84 |
+ projectRequest.DisplayName = o.DisplayName |
|
| 85 |
+ projectRequest.Annotations = make(map[string]string) |
|
| 86 |
+ projectRequest.Annotations["description"] = o.Description |
|
| 87 |
+ if _, err := o.Client.ProjectRequests().Create(projectRequest); err != nil {
|
|
| 88 |
+ return err |
|
| 89 |
+ } |
|
| 90 |
+ |
|
| 91 |
+ return nil |
|
| 92 |
+} |
| ... | ... |
@@ -36,6 +36,7 @@ const ( |
| 36 | 36 |
AdminRoleName = "admin" |
| 37 | 37 |
EditRoleName = "edit" |
| 38 | 38 |
ViewRoleName = "view" |
| 39 |
+ SelfProvisionerRoleName = "self-provisioner" |
|
| 39 | 40 |
BasicUserRoleName = "basic-user" |
| 40 | 41 |
StatusCheckerRoleName = "cluster-status" |
| 41 | 42 |
DeployerRoleName = "system:deployer" |
| ... | ... |
@@ -49,14 +50,15 @@ const ( |
| 49 | 49 |
|
| 50 | 50 |
// RoleBindings |
| 51 | 51 |
const ( |
| 52 |
- InternalComponentRoleBindingName = InternalComponentRoleName + "-binding" |
|
| 53 |
- DeployerRoleBindingName = DeployerRoleName + "-binding" |
|
| 54 |
- ClusterAdminRoleBindingName = ClusterAdminRoleName + "-binding" |
|
| 55 |
- BasicUserRoleBindingName = BasicUserRoleName + "-binding" |
|
| 52 |
+ SelfProvisionerRoleBindingName = SelfProvisionerRoleName + "s" |
|
| 53 |
+ InternalComponentRoleBindingName = InternalComponentRoleName + "s" |
|
| 54 |
+ DeployerRoleBindingName = DeployerRoleName + "s" |
|
| 55 |
+ ClusterAdminRoleBindingName = ClusterAdminRoleName + "s" |
|
| 56 |
+ BasicUserRoleBindingName = BasicUserRoleName + "s" |
|
| 56 | 57 |
DeleteTokensRoleBindingName = DeleteTokensRoleName + "-binding" |
| 57 | 58 |
StatusCheckerRoleBindingName = StatusCheckerRoleName + "-binding" |
| 58 |
- RouterRoleBindingName = RouterRoleName + "-binding" |
|
| 59 |
- RegistryRoleBindingName = RegistryRoleName + "-binding" |
|
| 59 |
+ RouterRoleBindingName = RouterRoleName + "s" |
|
| 60 |
+ RegistryRoleBindingName = RegistryRoleName + "s" |
|
| 60 | 61 |
|
| 61 |
- OpenshiftSharedResourceViewRoleBindingName = OpenshiftSharedResourceViewRoleName + "-binding" |
|
| 62 |
+ OpenshiftSharedResourceViewRoleBindingName = OpenshiftSharedResourceViewRoleName + "s" |
|
| 62 | 63 |
) |
| ... | ... |
@@ -102,12 +102,22 @@ func GetBootstrapMasterRoles(masterNamespace string) []authorizationapi.Role {
|
| 102 | 102 |
}, |
| 103 | 103 |
Rules: []authorizationapi.PolicyRule{
|
| 104 | 104 |
{Verbs: util.NewStringSet("get"), Resources: util.NewStringSet("users"), ResourceNames: util.NewStringSet("~")},
|
| 105 |
+ {Verbs: util.NewStringSet("get"), Resources: util.NewStringSet("projectrequests")},
|
|
| 105 | 106 |
{Verbs: util.NewStringSet("list"), Resources: util.NewStringSet("projects")},
|
| 106 | 107 |
{Verbs: util.NewStringSet("create"), Resources: util.NewStringSet("subjectaccessreviews"), AttributeRestrictions: runtime.EmbeddedObject{&authorizationapi.IsPersonalSubjectAccessReview{}}},
|
| 107 | 108 |
}, |
| 108 | 109 |
}, |
| 109 | 110 |
{
|
| 110 | 111 |
ObjectMeta: kapi.ObjectMeta{
|
| 112 |
+ Name: SelfProvisionerRoleName, |
|
| 113 |
+ Namespace: masterNamespace, |
|
| 114 |
+ }, |
|
| 115 |
+ Rules: []authorizationapi.PolicyRule{
|
|
| 116 |
+ {Verbs: util.NewStringSet("create"), Resources: util.NewStringSet("projectrequests")},
|
|
| 117 |
+ }, |
|
| 118 |
+ }, |
|
| 119 |
+ {
|
|
| 120 |
+ ObjectMeta: kapi.ObjectMeta{
|
|
| 111 | 121 |
Name: StatusCheckerRoleName, |
| 112 | 122 |
Namespace: masterNamespace, |
| 113 | 123 |
}, |
| ... | ... |
@@ -258,6 +268,17 @@ func GetBootstrapMasterRoleBindings(masterNamespace string) []authorizationapi.R |
| 258 | 258 |
}, |
| 259 | 259 |
{
|
| 260 | 260 |
ObjectMeta: kapi.ObjectMeta{
|
| 261 |
+ Name: SelfProvisionerRoleBindingName, |
|
| 262 |
+ Namespace: masterNamespace, |
|
| 263 |
+ }, |
|
| 264 |
+ RoleRef: kapi.ObjectReference{
|
|
| 265 |
+ Name: SelfProvisionerRoleName, |
|
| 266 |
+ Namespace: masterNamespace, |
|
| 267 |
+ }, |
|
| 268 |
+ Groups: util.NewStringSet(AuthenticatedGroup), |
|
| 269 |
+ }, |
|
| 270 |
+ {
|
|
| 271 |
+ ObjectMeta: kapi.ObjectMeta{
|
|
| 261 | 272 |
Name: DeleteTokensRoleBindingName, |
| 262 | 273 |
Namespace: masterNamespace, |
| 263 | 274 |
}, |
| ... | ... |
@@ -76,6 +76,7 @@ import ( |
| 76 | 76 |
projectapi "github.com/openshift/origin/pkg/project/api" |
| 77 | 77 |
projectcontroller "github.com/openshift/origin/pkg/project/controller" |
| 78 | 78 |
projectproxy "github.com/openshift/origin/pkg/project/registry/project/proxy" |
| 79 |
+ projectrequeststorage "github.com/openshift/origin/pkg/project/registry/projectrequest/delegated" |
|
| 79 | 80 |
routeallocationcontroller "github.com/openshift/origin/pkg/route/controller/allocation" |
| 80 | 81 |
routeetcd "github.com/openshift/origin/pkg/route/registry/etcd" |
| 81 | 82 |
routeregistry "github.com/openshift/origin/pkg/route/registry/route" |
| ... | ... |
@@ -154,7 +155,7 @@ func (c *MasterConfig) InstallProtectedAPI(container *restful.Container) []strin |
| 154 | 154 |
policyRegistry := policyregistry.NewRegistry(policyStorage) |
| 155 | 155 |
policyBindingStorage := policybindingetcd.NewStorage(c.EtcdHelper) |
| 156 | 156 |
policyBindingRegistry := policybindingregistry.NewRegistry(policyBindingStorage) |
| 157 |
- |
|
| 157 |
+ roleBindingRegistry := rolebindingregistry.NewVirtualRegistry(policyBindingRegistry, policyRegistry, c.Options.PolicyConfig.MasterAuthorizationNamespace) |
|
| 158 | 158 |
subjectAccessReviewStorage := subjectaccessreview.NewREST(c.Authorizer) |
| 159 | 159 |
subjectAccessReviewRegistry := subjectaccessreview.NewRegistry(subjectAccessReviewStorage) |
| 160 | 160 |
|
| ... | ... |
@@ -201,6 +202,8 @@ func (c *MasterConfig) InstallProtectedAPI(container *restful.Container) []strin |
| 201 | 201 |
GRFn: deployRollback.GenerateRollback, |
| 202 | 202 |
} |
| 203 | 203 |
|
| 204 |
+ projectStorage := projectproxy.NewREST(kclient.Namespaces(), c.ProjectAuthorizationCache) |
|
| 205 |
+ |
|
| 204 | 206 |
// initialize OpenShift API |
| 205 | 207 |
storage := map[string]rest.Storage{
|
| 206 | 208 |
"builds": buildregistry.NewREST(buildEtcd), |
| ... | ... |
@@ -232,7 +235,8 @@ func (c *MasterConfig) InstallProtectedAPI(container *restful.Container) []strin |
| 232 | 232 |
|
| 233 | 233 |
"routes": routeregistry.NewREST(routeEtcd, routeAllocator), |
| 234 | 234 |
|
| 235 |
- "projects": projectproxy.NewREST(kclient.Namespaces(), c.ProjectAuthorizationCache), |
|
| 235 |
+ "projects": projectStorage, |
|
| 236 |
+ "projectRequests": projectrequeststorage.NewREST(c.Options.PolicyConfig.MasterAuthorizationNamespace, roleBindingRegistry, *projectStorage), |
|
| 236 | 237 |
|
| 237 | 238 |
"users": userStorage, |
| 238 | 239 |
"identities": identityStorage, |
| ... | ... |
@@ -246,7 +250,7 @@ func (c *MasterConfig) InstallProtectedAPI(container *restful.Container) []strin |
| 246 | 246 |
"policies": policyStorage, |
| 247 | 247 |
"policyBindings": policyBindingStorage, |
| 248 | 248 |
"roles": roleregistry.NewREST(roleregistry.NewVirtualRegistry(policyRegistry)), |
| 249 |
- "roleBindings": rolebindingregistry.NewREST(rolebindingregistry.NewVirtualRegistry(policyBindingRegistry, policyRegistry, c.Options.PolicyConfig.MasterAuthorizationNamespace)), |
|
| 249 |
+ "roleBindings": rolebindingregistry.NewREST(roleBindingRegistry), |
|
| 250 | 250 |
"resourceAccessReviews": resourceaccessreviewregistry.NewREST(c.Authorizer), |
| 251 | 251 |
"subjectAccessReviews": subjectAccessReviewStorage, |
| 252 | 252 |
} |
| ... | ... |
@@ -8,8 +8,10 @@ func init() {
|
| 8 | 8 |
api.Scheme.AddKnownTypes("",
|
| 9 | 9 |
&Project{},
|
| 10 | 10 |
&ProjectList{},
|
| 11 |
+ &ProjectRequest{},
|
|
| 11 | 12 |
) |
| 12 | 13 |
} |
| 13 | 14 |
|
| 14 |
-func (*Project) IsAnAPIObject() {}
|
|
| 15 |
-func (*ProjectList) IsAnAPIObject() {}
|
|
| 15 |
+func (*ProjectRequest) IsAnAPIObject() {}
|
|
| 16 |
+func (*Project) IsAnAPIObject() {}
|
|
| 17 |
+func (*ProjectList) IsAnAPIObject() {}
|
| ... | ... |
@@ -8,8 +8,10 @@ func init() {
|
| 8 | 8 |
api.Scheme.AddKnownTypes("v1beta1",
|
| 9 | 9 |
&Project{},
|
| 10 | 10 |
&ProjectList{},
|
| 11 |
+ &ProjectRequest{},
|
|
| 11 | 12 |
) |
| 12 | 13 |
} |
| 13 | 14 |
|
| 14 |
-func (*Project) IsAnAPIObject() {}
|
|
| 15 |
-func (*ProjectList) IsAnAPIObject() {}
|
|
| 15 |
+func (*ProjectRequest) IsAnAPIObject() {}
|
|
| 16 |
+func (*Project) IsAnAPIObject() {}
|
|
| 17 |
+func (*ProjectList) IsAnAPIObject() {}
|
| ... | ... |
@@ -39,3 +39,9 @@ type Project struct {
|
| 39 | 39 |
// Status describes the current status of a Namespace |
| 40 | 40 |
Status ProjectStatus `json:"status,omitempty" description:"status describes the current status of a Project; read-only"` |
| 41 | 41 |
} |
| 42 |
+ |
|
| 43 |
+type ProjectRequest struct {
|
|
| 44 |
+ kapi.TypeMeta `json:",inline"` |
|
| 45 |
+ kapi.ObjectMeta `json:"metadata,omitempty"` |
|
| 46 |
+ DisplayName string `json:"displayName,omitempty"` |
|
| 47 |
+} |
| ... | ... |
@@ -8,8 +8,10 @@ func init() {
|
| 8 | 8 |
api.Scheme.AddKnownTypes("v1beta3",
|
| 9 | 9 |
&Project{},
|
| 10 | 10 |
&ProjectList{},
|
| 11 |
+ &ProjectRequest{},
|
|
| 11 | 12 |
) |
| 12 | 13 |
} |
| 13 | 14 |
|
| 14 |
-func (*Project) IsAnAPIObject() {}
|
|
| 15 |
-func (*ProjectList) IsAnAPIObject() {}
|
|
| 15 |
+func (*ProjectRequest) IsAnAPIObject() {}
|
|
| 16 |
+func (*Project) IsAnAPIObject() {}
|
|
| 17 |
+func (*ProjectList) IsAnAPIObject() {}
|
| ... | ... |
@@ -39,3 +39,9 @@ type Project struct {
|
| 39 | 39 |
// Status describes the current status of a Namespace |
| 40 | 40 |
Status ProjectStatus `json:"status,omitempty" description:"status describes the current status of a Project; read-only"` |
| 41 | 41 |
} |
| 42 |
+ |
|
| 43 |
+type ProjectRequest struct {
|
|
| 44 |
+ kapi.TypeMeta `json:",inline"` |
|
| 45 |
+ kapi.ObjectMeta `json:"metadata,omitempty"` |
|
| 46 |
+ DisplayName string `json:"displayName,omitempty"` |
|
| 47 |
+} |
| ... | ... |
@@ -39,3 +39,11 @@ func ValidateProjectUpdate(newProject *api.Project, oldProject *api.Project) fie |
| 39 | 39 |
newProject.Status = oldProject.Status |
| 40 | 40 |
return allErrs |
| 41 | 41 |
} |
| 42 |
+ |
|
| 43 |
+func ValidateProjectRequest(request *api.ProjectRequest) fielderrors.ValidationErrorList {
|
|
| 44 |
+ project := &api.Project{}
|
|
| 45 |
+ project.ObjectMeta = request.ObjectMeta |
|
| 46 |
+ project.DisplayName = request.DisplayName |
|
| 47 |
+ |
|
| 48 |
+ return ValidateProject(project) |
|
| 49 |
+} |
| 42 | 50 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,67 @@ |
| 0 |
+package delegated |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api" |
|
| 4 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/rest" |
|
| 5 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" |
|
| 6 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/util" |
|
| 7 |
+ |
|
| 8 |
+ authorizationapi "github.com/openshift/origin/pkg/authorization/api" |
|
| 9 |
+ "github.com/openshift/origin/pkg/authorization/registry/rolebinding" |
|
| 10 |
+ |
|
| 11 |
+ projectapi "github.com/openshift/origin/pkg/project/api" |
|
| 12 |
+ projectstorage "github.com/openshift/origin/pkg/project/registry/project/proxy" |
|
| 13 |
+ projectrequestregistry "github.com/openshift/origin/pkg/project/registry/projectrequest" |
|
| 14 |
+) |
|
| 15 |
+ |
|
| 16 |
+type REST struct {
|
|
| 17 |
+ masterNamespace string |
|
| 18 |
+ roleBindingRegistry rolebinding.Registry |
|
| 19 |
+ |
|
| 20 |
+ projectStorage projectstorage.REST |
|
| 21 |
+} |
|
| 22 |
+ |
|
| 23 |
+func NewREST(masterNamespace string, roleBindingRegistry rolebinding.Registry, projectStorage projectstorage.REST) *REST {
|
|
| 24 |
+ return &REST{
|
|
| 25 |
+ masterNamespace: masterNamespace, |
|
| 26 |
+ roleBindingRegistry: roleBindingRegistry, |
|
| 27 |
+ projectStorage: projectStorage, |
|
| 28 |
+ } |
|
| 29 |
+} |
|
| 30 |
+ |
|
| 31 |
+func (r *REST) New() runtime.Object {
|
|
| 32 |
+ return &projectapi.ProjectRequest{}
|
|
| 33 |
+} |
|
| 34 |
+ |
|
| 35 |
+func (r *REST) Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, error) {
|
|
| 36 |
+ if err := rest.BeforeCreate(projectrequestregistry.Strategy, ctx, obj); err != nil {
|
|
| 37 |
+ return nil, err |
|
| 38 |
+ } |
|
| 39 |
+ |
|
| 40 |
+ projectRequest := obj.(*projectapi.ProjectRequest) |
|
| 41 |
+ |
|
| 42 |
+ project := &projectapi.Project{}
|
|
| 43 |
+ project.ObjectMeta = projectRequest.ObjectMeta |
|
| 44 |
+ project.DisplayName = projectRequest.DisplayName |
|
| 45 |
+ |
|
| 46 |
+ projectObj, err := r.projectStorage.Create(ctx, project) |
|
| 47 |
+ if err != nil {
|
|
| 48 |
+ return nil, err |
|
| 49 |
+ } |
|
| 50 |
+ realizedProject := projectObj.(*projectapi.Project) |
|
| 51 |
+ |
|
| 52 |
+ adminBinding := &authorizationapi.RoleBinding{}
|
|
| 53 |
+ adminBinding.Namespace = realizedProject.Name |
|
| 54 |
+ adminBinding.Name = "admins" |
|
| 55 |
+ adminBinding.RoleRef = kapi.ObjectReference{Namespace: r.masterNamespace, Name: "admin"}
|
|
| 56 |
+ if userInfo, exists := kapi.UserFrom(ctx); exists {
|
|
| 57 |
+ adminBinding.Users = util.NewStringSet(userInfo.GetName()) |
|
| 58 |
+ } |
|
| 59 |
+ |
|
| 60 |
+ projectContext := kapi.WithNamespace(ctx, realizedProject.Name) |
|
| 61 |
+ if err := r.roleBindingRegistry.CreateRoleBinding(projectContext, adminBinding, true); err != nil {
|
|
| 62 |
+ return nil, err |
|
| 63 |
+ } |
|
| 64 |
+ |
|
| 65 |
+ return realizedProject, nil |
|
| 66 |
+} |
| 0 | 67 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,47 @@ |
| 0 |
+package projectrequest |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api" |
|
| 4 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" |
|
| 5 |
+ "github.com/GoogleCloudPlatform/kubernetes/pkg/util/fielderrors" |
|
| 6 |
+ |
|
| 7 |
+ projectapi "github.com/openshift/origin/pkg/project/api" |
|
| 8 |
+ projectvalidation "github.com/openshift/origin/pkg/project/api/validation" |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+// strategy implements behavior for OAuthClient objects |
|
| 12 |
+type strategy struct {
|
|
| 13 |
+ runtime.ObjectTyper |
|
| 14 |
+} |
|
| 15 |
+ |
|
| 16 |
+var Strategy = strategy{kapi.Scheme}
|
|
| 17 |
+ |
|
| 18 |
+func (strategy) PrepareForUpdate(obj, old runtime.Object) {}
|
|
| 19 |
+ |
|
| 20 |
+// NamespaceScoped is false for projectrequest objects |
|
| 21 |
+func (strategy) NamespaceScoped() bool {
|
|
| 22 |
+ return false |
|
| 23 |
+} |
|
| 24 |
+ |
|
| 25 |
+func (strategy) GenerateName(base string) string {
|
|
| 26 |
+ return base |
|
| 27 |
+} |
|
| 28 |
+ |
|
| 29 |
+func (strategy) PrepareForCreate(obj runtime.Object) {
|
|
| 30 |
+} |
|
| 31 |
+ |
|
| 32 |
+// Validate validates a new client |
|
| 33 |
+func (strategy) Validate(ctx kapi.Context, obj runtime.Object) fielderrors.ValidationErrorList {
|
|
| 34 |
+ projectrequest := obj.(*projectapi.ProjectRequest) |
|
| 35 |
+ return projectvalidation.ValidateProjectRequest(projectrequest) |
|
| 36 |
+} |
|
| 37 |
+ |
|
| 38 |
+// ValidateUpdate validates a client update |
|
| 39 |
+func (strategy) ValidateUpdate(ctx kapi.Context, obj runtime.Object, old runtime.Object) fielderrors.ValidationErrorList {
|
|
| 40 |
+ return nil |
|
| 41 |
+} |
|
| 42 |
+ |
|
| 43 |
+// AllowCreateOnUpdate is false for OAuth objects |
|
| 44 |
+func (strategy) AllowCreateOnUpdate() bool {
|
|
| 45 |
+ return false |
|
| 46 |
+} |
| 0 | 47 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,60 @@ |
| 0 |
+// +build integration,!no-etcd |
|
| 1 |
+ |
|
| 2 |
+package integration |
|
| 3 |
+ |
|
| 4 |
+import ( |
|
| 5 |
+ "testing" |
|
| 6 |
+ "time" |
|
| 7 |
+ |
|
| 8 |
+ "github.com/openshift/origin/pkg/client" |
|
| 9 |
+ |
|
| 10 |
+ osc "github.com/openshift/origin/pkg/cmd/cli/cmd" |
|
| 11 |
+ "github.com/openshift/origin/pkg/cmd/util/tokencmd" |
|
| 12 |
+ testutil "github.com/openshift/origin/test/util" |
|
| 13 |
+) |
|
| 14 |
+ |
|
| 15 |
+func TestUnprivilegedNewProject(t *testing.T) {
|
|
| 16 |
+ _, clusterAdminKubeConfig, err := testutil.StartTestMaster() |
|
| 17 |
+ if err != nil {
|
|
| 18 |
+ t.Fatalf("unexpected error: %v", err)
|
|
| 19 |
+ } |
|
| 20 |
+ |
|
| 21 |
+ clusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig) |
|
| 22 |
+ if err != nil {
|
|
| 23 |
+ t.Errorf("unexpected error: %v", err)
|
|
| 24 |
+ } |
|
| 25 |
+ |
|
| 26 |
+ valerieClientConfig := *clusterAdminClientConfig |
|
| 27 |
+ valerieClientConfig.Username = "" |
|
| 28 |
+ valerieClientConfig.Password = "" |
|
| 29 |
+ valerieClientConfig.BearerToken = "" |
|
| 30 |
+ valerieClientConfig.CertFile = "" |
|
| 31 |
+ valerieClientConfig.KeyFile = "" |
|
| 32 |
+ valerieClientConfig.CertData = nil |
|
| 33 |
+ valerieClientConfig.KeyData = nil |
|
| 34 |
+ |
|
| 35 |
+ accessToken, err := tokencmd.RequestToken(&valerieClientConfig, nil, "valerie", "security!") |
|
| 36 |
+ if err != nil {
|
|
| 37 |
+ t.Fatalf("unexpected error: %v", err)
|
|
| 38 |
+ } |
|
| 39 |
+ |
|
| 40 |
+ valerieClientConfig.BearerToken = accessToken |
|
| 41 |
+ valerieOpenshiftClient, err := client.New(&valerieClientConfig) |
|
| 42 |
+ if err != nil {
|
|
| 43 |
+ t.Fatalf("unexpected error: %v", err)
|
|
| 44 |
+ } |
|
| 45 |
+ |
|
| 46 |
+ requestProject := osc.NewProjectOptions{
|
|
| 47 |
+ ProjectName: "new-project", |
|
| 48 |
+ DisplayName: "display name here", |
|
| 49 |
+ Description: "the special description", |
|
| 50 |
+ |
|
| 51 |
+ Client: valerieOpenshiftClient, |
|
| 52 |
+ } |
|
| 53 |
+ |
|
| 54 |
+ if err := requestProject.Run(); err != nil {
|
|
| 55 |
+ t.Errorf("unexpected error: %v", err)
|
|
| 56 |
+ } |
|
| 57 |
+ |
|
| 58 |
+ waitForProject(t, valerieOpenshiftClient, "new-project", 5*time.Second, 10) |
|
| 59 |
+} |