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/ProjectRequestTest.java b/kubernetes-tests/src/test/java/io/fabric8/openshift/client/server/mock/ProjectRequestTest.java index e0c5e506dd7..3a064b52197 100644 --- a/kubernetes-tests/src/test/java/io/fabric8/openshift/client/server/mock/ProjectRequestTest.java +++ b/kubernetes-tests/src/test/java/io/fabric8/openshift/client/server/mock/ProjectRequestTest.java @@ -16,8 +16,11 @@ package io.fabric8.openshift.client.server.mock; +import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.Status; import io.fabric8.kubernetes.api.model.StatusBuilder; +import io.fabric8.kubernetes.api.model.rbac.RoleBindingBuilder; +import io.fabric8.openshift.api.model.ProjectBuilder; import io.fabric8.openshift.api.model.ProjectRequest; import io.fabric8.openshift.api.model.ProjectRequestBuilder; import io.fabric8.openshift.client.OpenShiftClient; @@ -26,6 +29,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.assertNotNull; @@ -58,4 +64,107 @@ public void testCreate() { assertNotNull(result); assertEquals(req1, result); } + + @Test + void testCreateUsingParameters() { + // 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.projectrequests().create(name, description, displayName, adminUser, requestingUser); + + // Then + assertNotNull(result); + assertEquals(5, result.size()); + } } 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..c072e37d720 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 @@ -15,15 +15,27 @@ */ package io.fabric8.openshift.client.dsl; +import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.Status; import io.fabric8.kubernetes.client.dsl.Createable; import io.fabric8.kubernetes.client.dsl.Listable; import io.fabric8.openshift.api.model.DoneableProjectRequest; import io.fabric8.openshift.api.model.ProjectRequest; +import java.util.List; + public interface ProjectRequestOperation extends Createable, Listable { - - + /** + * 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 create(String name, String description, String displayName, String adminUser, String requestingUser); } 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..8e44599f05c 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 @@ -15,11 +15,17 @@ */ 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.ListOptions; import io.fabric8.kubernetes.api.model.ListOptionsBuilder; +import io.fabric8.kubernetes.api.model.rbac.RoleBinding; +import io.fabric8.kubernetes.api.model.rbac.RoleBindingBuilder; import io.fabric8.kubernetes.client.dsl.base.OperationContext; -import io.fabric8.kubernetes.client.utils.URLUtils; +import io.fabric8.kubernetes.client.dsl.internal.NamespaceVisitFromServerGetWatchDeleteRecreateWaitApplicableListImpl; import io.fabric8.kubernetes.client.utils.Utils; +import io.fabric8.openshift.api.model.Project; +import io.fabric8.openshift.api.model.ProjectBuilder; import okhttp3.HttpUrl; import okhttp3.OkHttpClient; import okhttp3.Request; @@ -34,6 +40,8 @@ import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.ExecutionException; import static io.fabric8.openshift.client.OpenShiftAPIGroups.PROJECT; @@ -41,6 +49,12 @@ // TODO: Check why this class does not extend OpenshiftOperation, then the getRoot method can be removed public class ProjectRequestsOperationImpl extends OperationSupport implements ProjectRequestOperation { + 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 ProjectRequestsOperationImpl(OkHttpClient client, OpenShiftConfig config) { this(new OperationContext().withOkhttpClient(client).withConfig(config)); } @@ -165,4 +179,114 @@ public Status list(ListOptions listOptions) { public ProjectRequest getItem() { return (ProjectRequest) context.getItem(); } + + @Override + public List create(String name, String description, String displayName, String adminUser, String requestingUser) { + List result = new ArrayList<>(); + Project project = getProject(name, description, displayName, requestingUser); + List projectRoleBindings = getNewProjectRoleBindings(name, adminUser); + + // Create Project + ProjectOperationsImpl projectOperations = new ProjectOperationsImpl(client, OpenShiftConfig.wrap(config)); + result.add(projectOperations.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 getProject(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 getNewProjectRoleBindings(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; + } }