From b6a3eed3c269b3858f1e2d73e303ffe56af8337e Mon Sep 17 00:00:00 2001 From: Rohan Kumar Date: Wed, 29 Jul 2020 17:28:08 +0530 Subject: [PATCH] Fix #2311: Add Support for creating bootstrap project template Just like `oc adm create-bootstrap-project-template` generates a template yaml for project request resources to be created; OpenShiftClient should be able to generate + Apply resources based upon project request name, description, display name, requesting user and admin user. It should create the following resources: - Project (actual project to be created) - RoleBinding system:deployers - RoleBinding system:image-builders - RoleBinding system:image-pullers - RoleBinding admin --- CHANGELOG.md | 1 + .../client/server/mock/ProjectTest.java | 108 +++++++++++++++ .../client/DefaultOpenShiftClient.java | 2 +- .../openshift/client/OpenShiftClient.java | 2 +- .../client/dsl/ProjectOperation.java | 39 ++++++ .../client/dsl/ProjectRequestOperation.java | 2 - .../dsl/internal/ProjectOperationsImpl.java | 128 +++++++++++++++++- .../ProjectRequestsOperationImpl.java | 15 +- .../client/osgi/ManagedOpenShiftClient.java | 2 +- 9 files changed, 285 insertions(+), 14 deletions(-) create mode 100644 openshift-client/src/main/java/io/fabric8/openshift/client/dsl/ProjectOperation.java diff --git a/CHANGELOG.md b/CHANGELOG.md index bbd9422fa50..b881596fecd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ * Fix #2292: Update createOrReplace to do replace when create fails with conflict #### New Features +* Fix #2311: Add Support for creating bootstrap project template * Fix #2287: Add support for V1 and V1Beta1 CustomResourceDefinition * Fix #2319: Create Config without using auto-configure functionality or setting env variables diff --git a/kubernetes-tests/src/test/java/io/fabric8/openshift/client/server/mock/ProjectTest.java b/kubernetes-tests/src/test/java/io/fabric8/openshift/client/server/mock/ProjectTest.java index ad6b4e0f726..fef0079e9eb 100644 --- a/kubernetes-tests/src/test/java/io/fabric8/openshift/client/server/mock/ProjectTest.java +++ b/kubernetes-tests/src/test/java/io/fabric8/openshift/client/server/mock/ProjectTest.java @@ -16,6 +16,8 @@ package io.fabric8.openshift.client.server.mock; +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.api.model.rbac.RoleBindingBuilder; import io.fabric8.openshift.api.model.Project; import io.fabric8.openshift.api.model.ProjectBuilder; import io.fabric8.openshift.api.model.ProjectList; @@ -26,6 +28,9 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.migrationsupport.rules.EnableRuleMigrationSupport; +import java.net.HttpURLConnection; +import java.util.List; + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -93,4 +98,107 @@ public void testDelete() { deleted = client.projects().withName("project3").delete(); assertFalse(deleted); } + + @Test + void testCreateProjectAndRoleBindings() { + // Given + String name = "test-project"; + String displayName = "test-project"; + String description = "test project"; + String requestingUser = "request-user"; + String adminUser = "admin-user"; + server.expect().post().withPath("/apis/project.openshift.io/v1/projects") + .andReturn(HttpURLConnection.HTTP_CREATED, new ProjectBuilder() + .withNewMetadata() + .addToAnnotations("openshift.io/description", description) + .addToAnnotations("openshift.io/display-name", displayName) + .addToAnnotations("openshift.io/requester", requestingUser) + .withName("test-project") + .endMetadata() + .build()).once(); + server.expect().post().withPath("/apis/rbac.authorization.k8s.io/v1/namespaces/test-project/rolebindings") + .andReturn(HttpURLConnection.HTTP_CREATED, new RoleBindingBuilder() + .withNewMetadata() + .addToAnnotations("openshift.io/description", "Allows all pods in this namespace to pull images from this namespace. It is auto-managed by a controller; remove subjects to disable.") + .withName("system:image-pullers") + .withNamespace(name) + .endMetadata() + .withNewRoleRef() + .withApiGroup("rbac.authorization.k8s.io") + .withKind("ClusterRole") + .withName("system:image-puller") + .endRoleRef() + .addNewSubject() + .withApiGroup("rbac.authorization.k8s.io") + .withKind("Group") + .withName("system:serviceaccounts:" + name) + .endSubject() + .build()).once(); + server.expect().post().withPath("/apis/rbac.authorization.k8s.io/v1/namespaces/test-project/rolebindings") + .andReturn(HttpURLConnection.HTTP_CREATED, new RoleBindingBuilder() + .withNewMetadata() + .addToAnnotations("openshift.io/description", "Allows builds in this namespace to push images to" + + "this namespace. It is auto-managed by a controller; remove subjects to disable.") + .withName("system:image-builders") + .withNamespace(name) + .endMetadata() + .withNewRoleRef() + .withApiGroup("rbac.authorization.k8s.io") + .withKind("ClusterRole") + .withName("system:image-builder") + .endRoleRef() + .addNewSubject() + .withKind("ServiceAccount") + .withName("builder") + .withNamespace(name) + .endSubject() + .build()).once(); + server.expect().post().withPath("/apis/rbac.authorization.k8s.io/v1/namespaces/test-project/rolebindings") + .andReturn(HttpURLConnection.HTTP_CREATED, new RoleBindingBuilder() + .withNewMetadata() + .addToAnnotations("openshift.io/description", " Allows deploymentconfigs in this namespace to rollout" + + " pods in this namespace. It is auto-managed by a controller; remove subjects" + + " to disable.") + .withName("system:deployers") + .withNamespace(name) + .endMetadata() + .withNewRoleRef() + .withApiGroup("rbac.authorization.k8s.io") + .withKind("ClusterRole") + .withName("system:deployer") + .endRoleRef() + .addNewSubject() + .withKind("ServiceAccount") + .withName("deployer") + .withNamespace(name) + .endSubject() + .build()).once(); + server.expect().post().withPath("/apis/rbac.authorization.k8s.io/v1/namespaces/test-project/rolebindings") + .andReturn(HttpURLConnection.HTTP_CREATED, new RoleBindingBuilder() + .withNewMetadata() + .withName("admin") + .withNamespace(name) + .endMetadata() + .withNewRoleRef() + .withApiGroup("rbac.authorization.k8s.io") + .withKind("ClusterRole") + .withName("admin") + .endRoleRef() + .addNewSubject() + .withApiGroup("rbac.authorization.k8s.io") + .withKind("User") + .withName(adminUser) + .endSubject() + .build()).once(); + + + OpenShiftClient client = server.getOpenshiftClient(); + + // When + List result = client.projects().createProjectAndRoleBindings(name, description, displayName, adminUser, requestingUser); + + // Then + assertNotNull(result); + assertEquals(5, result.size()); + } } diff --git a/openshift-client/src/main/java/io/fabric8/openshift/client/DefaultOpenShiftClient.java b/openshift-client/src/main/java/io/fabric8/openshift/client/DefaultOpenShiftClient.java index 1ce7c24f6e5..75b0184afa1 100644 --- a/openshift-client/src/main/java/io/fabric8/openshift/client/DefaultOpenShiftClient.java +++ b/openshift-client/src/main/java/io/fabric8/openshift/client/DefaultOpenShiftClient.java @@ -434,7 +434,7 @@ public NonNamespaceOperation> projects() { + public ProjectOperation projects() { return new ProjectOperationsImpl(httpClient, OpenShiftConfig.wrap(getConfiguration())); } diff --git a/openshift-client/src/main/java/io/fabric8/openshift/client/OpenShiftClient.java b/openshift-client/src/main/java/io/fabric8/openshift/client/OpenShiftClient.java index bc24027e28b..659241e3358 100644 --- a/openshift-client/src/main/java/io/fabric8/openshift/client/OpenShiftClient.java +++ b/openshift-client/src/main/java/io/fabric8/openshift/client/OpenShiftClient.java @@ -85,7 +85,7 @@ public interface OpenShiftClient extends KubernetesClient { NonNamespaceOperation> oAuthClients(); - NonNamespaceOperation> projects(); + ProjectOperation projects(); ProjectRequestOperation projectrequests(); diff --git a/openshift-client/src/main/java/io/fabric8/openshift/client/dsl/ProjectOperation.java b/openshift-client/src/main/java/io/fabric8/openshift/client/dsl/ProjectOperation.java new file mode 100644 index 00000000000..c1a0faa3721 --- /dev/null +++ b/openshift-client/src/main/java/io/fabric8/openshift/client/dsl/ProjectOperation.java @@ -0,0 +1,39 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.openshift.client.dsl; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation; +import io.fabric8.kubernetes.client.dsl.Resource; +import io.fabric8.openshift.api.model.DoneableProject; +import io.fabric8.openshift.api.model.Project; +import io.fabric8.openshift.api.model.ProjectList; + +import java.util.List; + +public interface ProjectOperation extends NonNamespaceOperation> { + /** + * Creating Bootstrap Project Template + * + * @param name project name + * @param description project description + * @param displayName project display name + * @param adminUser project admin user + * @param requestingUser project requesting user + * @return list of items created + */ + List createProjectAndRoleBindings(String name, String description, String displayName, String adminUser, String requestingUser); +} diff --git a/openshift-client/src/main/java/io/fabric8/openshift/client/dsl/ProjectRequestOperation.java b/openshift-client/src/main/java/io/fabric8/openshift/client/dsl/ProjectRequestOperation.java index 96867077c6f..d7ad5174769 100644 --- a/openshift-client/src/main/java/io/fabric8/openshift/client/dsl/ProjectRequestOperation.java +++ b/openshift-client/src/main/java/io/fabric8/openshift/client/dsl/ProjectRequestOperation.java @@ -24,6 +24,4 @@ public interface ProjectRequestOperation extends Createable, Listable { - - } diff --git a/openshift-client/src/main/java/io/fabric8/openshift/client/dsl/internal/ProjectOperationsImpl.java b/openshift-client/src/main/java/io/fabric8/openshift/client/dsl/internal/ProjectOperationsImpl.java index 542c693df38..a4b11f77b93 100644 --- a/openshift-client/src/main/java/io/fabric8/openshift/client/dsl/internal/ProjectOperationsImpl.java +++ b/openshift-client/src/main/java/io/fabric8/openshift/client/dsl/internal/ProjectOperationsImpl.java @@ -15,21 +15,34 @@ */ package io.fabric8.openshift.client.dsl.internal; +import io.fabric8.kubernetes.api.model.DeletionPropagation; +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.api.model.rbac.RoleBinding; +import io.fabric8.kubernetes.api.model.rbac.RoleBindingBuilder; import io.fabric8.kubernetes.client.dsl.Resource; import io.fabric8.kubernetes.client.dsl.base.OperationContext; +import io.fabric8.kubernetes.client.dsl.internal.NamespaceVisitFromServerGetWatchDeleteRecreateWaitApplicableListImpl; +import io.fabric8.openshift.api.model.ProjectBuilder; +import io.fabric8.openshift.client.dsl.ProjectOperation; import okhttp3.OkHttpClient; import io.fabric8.openshift.api.model.DoneableProject; import io.fabric8.openshift.api.model.Project; import io.fabric8.openshift.api.model.ProjectList; import io.fabric8.openshift.client.OpenShiftConfig; -import java.util.Map; -import java.util.TreeMap; +import java.util.ArrayList; +import java.util.List; import static io.fabric8.openshift.client.OpenShiftAPIGroups.PROJECT; public class ProjectOperationsImpl extends OpenShiftOperation> { + Resource> implements ProjectOperation { + public static final String OPENSHIFT_IO_DESCRIPTION_ANNOTATION = "openshift.io/description"; + public static final String OPENSHIFT_IO_DISPLAY_NAME_ANNOTATION = "openshift.io/display-name"; + public static final String OPENSHIFT_IO_REQUESTER_ANNOTATION = "openshift.io/requester"; + public static final String RBAC_AUTHORIZATION_APIGROUP = "rbac.authorization.k8s.io"; + public static final String CLUSTER_ROLE = "ClusterRole"; + public ProjectOperationsImpl(OkHttpClient client, OpenShiftConfig config) { this(new OperationContext().withOkhttpClient(client).withConfig(config)); @@ -51,4 +64,113 @@ public ProjectOperationsImpl newInstance(OperationContext context) { public boolean isResourceNamespaced() { return false; } + + @Override + public List createProjectAndRoleBindings(String name, String description, String displayName, String adminUser, String requestingUser) { + List result = new ArrayList<>(); + Project project = initProject(name, description, displayName, requestingUser); + List projectRoleBindings = initRoleBindings(name, adminUser); + + // Create Project + result.add(create(project)); + + // Create Role Bindings + NamespaceVisitFromServerGetWatchDeleteRecreateWaitApplicableListImpl listOp = new NamespaceVisitFromServerGetWatchDeleteRecreateWaitApplicableListImpl(client, config, getNamespace(), null, false, false, new ArrayList<>(), projectRoleBindings, null, DeletionPropagation.BACKGROUND, true) {}; + result.addAll(listOp.createOrReplace()); + + return result; + } + + private Project initProject(String name, String description, String displayName, String requestingUser) { + return new ProjectBuilder() + .withNewMetadata() + .addToAnnotations(OPENSHIFT_IO_DESCRIPTION_ANNOTATION, description) + .addToAnnotations(OPENSHIFT_IO_DISPLAY_NAME_ANNOTATION, displayName) + .addToAnnotations(OPENSHIFT_IO_REQUESTER_ANNOTATION, requestingUser) + .withName(name) + .endMetadata() + .build(); + } + + private List initRoleBindings(String name, String adminUser) { + RoleBinding roleBindingPuller = new RoleBindingBuilder() + .withNewMetadata() + .addToAnnotations(OPENSHIFT_IO_DESCRIPTION_ANNOTATION, "Allows all pods in this namespace to pull images from this namespace. It is auto-managed by a controller; remove subjects to disable.") + .withName("system:image-pullers") + .withNamespace(name) + .endMetadata() + .withNewRoleRef() + .withApiGroup(RBAC_AUTHORIZATION_APIGROUP) + .withKind(CLUSTER_ROLE) + .withName("system:image-puller") + .endRoleRef() + .addNewSubject() + .withApiGroup(RBAC_AUTHORIZATION_APIGROUP) + .withKind("Group") + .withName("system:serviceaccounts:" + name) + .endSubject() + .build(); + RoleBinding roleBindingBuilder = new RoleBindingBuilder() + .withNewMetadata() + .addToAnnotations(OPENSHIFT_IO_DESCRIPTION_ANNOTATION, "Allows builds in this namespace to push images to" + + "this namespace. It is auto-managed by a controller; remove subjects to disable.") + .withName("system:image-builders") + .withNamespace(name) + .endMetadata() + .withNewRoleRef() + .withApiGroup(RBAC_AUTHORIZATION_APIGROUP) + .withKind(CLUSTER_ROLE) + .withName("system:image-builder") + .endRoleRef() + .addNewSubject() + .withKind("ServiceAccount") + .withName("builder") + .withNamespace(name) + .endSubject() + .build(); + RoleBinding roleBindingDeployer = new RoleBindingBuilder() + .withNewMetadata() + .addToAnnotations(OPENSHIFT_IO_DESCRIPTION_ANNOTATION, " Allows deploymentconfigs in this namespace to rollout" + + " pods in this namespace. It is auto-managed by a controller; remove subjects" + + " to disable.") + .withName("system:deployers") + .withNamespace(name) + .endMetadata() + .withNewRoleRef() + .withApiGroup(RBAC_AUTHORIZATION_APIGROUP) + .withKind(CLUSTER_ROLE) + .withName("system:deployer") + .endRoleRef() + .addNewSubject() + .withKind("ServiceAccount") + .withName("deployer") + .withNamespace(name) + .endSubject() + .build(); + + RoleBinding roleBindingAdmin = new RoleBindingBuilder() + .withNewMetadata() + .withName("admin") + .withNamespace(name) + .endMetadata() + .withNewRoleRef() + .withApiGroup(RBAC_AUTHORIZATION_APIGROUP) + .withKind(CLUSTER_ROLE) + .withName("admin") + .endRoleRef() + .addNewSubject() + .withApiGroup(RBAC_AUTHORIZATION_APIGROUP) + .withKind("User") + .withName(adminUser) + .endSubject() + .build(); + + List resources = new ArrayList<>(); + resources.add(roleBindingPuller); + resources.add(roleBindingBuilder); + resources.add(roleBindingDeployer); + resources.add(roleBindingAdmin); + + return resources; + } } diff --git a/openshift-client/src/main/java/io/fabric8/openshift/client/dsl/internal/ProjectRequestsOperationImpl.java b/openshift-client/src/main/java/io/fabric8/openshift/client/dsl/internal/ProjectRequestsOperationImpl.java index e7becbffdf5..83b2f38b806 100644 --- a/openshift-client/src/main/java/io/fabric8/openshift/client/dsl/internal/ProjectRequestsOperationImpl.java +++ b/openshift-client/src/main/java/io/fabric8/openshift/client/dsl/internal/ProjectRequestsOperationImpl.java @@ -17,20 +17,23 @@ import io.fabric8.kubernetes.api.model.ListOptions; import io.fabric8.kubernetes.api.model.ListOptionsBuilder; -import io.fabric8.kubernetes.client.dsl.base.OperationContext; -import io.fabric8.kubernetes.client.utils.URLUtils; -import io.fabric8.kubernetes.client.utils.Utils; -import okhttp3.HttpUrl; -import okhttp3.OkHttpClient; -import okhttp3.Request; import io.fabric8.kubernetes.api.model.Status; import io.fabric8.kubernetes.client.KubernetesClientException; +import io.fabric8.kubernetes.client.dsl.base.OperationContext; import io.fabric8.kubernetes.client.dsl.base.OperationSupport; +import io.fabric8.kubernetes.client.utils.Utils; import io.fabric8.openshift.api.model.DoneableProjectRequest; import io.fabric8.openshift.api.model.ProjectRequest; import io.fabric8.openshift.client.OpenShiftConfig; import io.fabric8.openshift.client.dsl.ProjectRequestOperation; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.concurrent.ExecutionException; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; diff --git a/openshift-client/src/main/java/io/fabric8/openshift/client/osgi/ManagedOpenShiftClient.java b/openshift-client/src/main/java/io/fabric8/openshift/client/osgi/ManagedOpenShiftClient.java index 9a1a5469f2a..94bccd19780 100644 --- a/openshift-client/src/main/java/io/fabric8/openshift/client/osgi/ManagedOpenShiftClient.java +++ b/openshift-client/src/main/java/io/fabric8/openshift/client/osgi/ManagedOpenShiftClient.java @@ -233,7 +233,7 @@ public NonNamespaceOperation> projects() { + public ProjectOperation projects() { return delegate.projects(); }