LCOV - code coverage report
Current view: top level - sshd/commands - SetParentCommand.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 3 64 4.7 %
Date: 2022-11-19 15:00:39 Functions: 1 5 20.0 %

          Line data    Source code
       1             : // Copyright (C) 2010 The Android Open Source Project
       2             : //
       3             : // Licensed under the Apache License, Version 2.0 (the "License");
       4             : // you may not use this file except in compliance with the License.
       5             : // You may obtain a copy of the License at
       6             : //
       7             : // http://www.apache.org/licenses/LICENSE-2.0
       8             : //
       9             : // Unless required by applicable law or agreed to in writing, software
      10             : // distributed under the License is distributed on an "AS IS" BASIS,
      11             : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      12             : // See the License for the specific language governing permissions and
      13             : // limitations under the License
      14             : 
      15             : package com.google.gerrit.sshd.commands;
      16             : 
      17             : import static com.google.gerrit.server.project.ProjectCache.illegalState;
      18             : import static java.util.stream.Collectors.toList;
      19             : 
      20             : import com.google.gerrit.entities.Project;
      21             : import com.google.gerrit.extensions.api.projects.ParentInput;
      22             : import com.google.gerrit.extensions.common.ProjectInfo;
      23             : import com.google.gerrit.extensions.restapi.AuthException;
      24             : import com.google.gerrit.extensions.restapi.BadRequestException;
      25             : import com.google.gerrit.extensions.restapi.ResourceConflictException;
      26             : import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
      27             : import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
      28             : import com.google.gerrit.server.permissions.PermissionBackendException;
      29             : import com.google.gerrit.server.project.ProjectCache;
      30             : import com.google.gerrit.server.project.ProjectResource;
      31             : import com.google.gerrit.server.project.ProjectState;
      32             : import com.google.gerrit.server.restapi.project.ListChildProjects;
      33             : import com.google.gerrit.server.restapi.project.SetParent;
      34             : import com.google.gerrit.sshd.CommandMetaData;
      35             : import com.google.gerrit.sshd.SshCommand;
      36             : import com.google.inject.Inject;
      37             : import java.io.IOException;
      38             : import java.util.ArrayList;
      39             : import java.util.Collections;
      40             : import java.util.List;
      41             : import java.util.Optional;
      42             : import java.util.Set;
      43             : import org.kohsuke.args4j.Argument;
      44             : import org.kohsuke.args4j.Option;
      45             : 
      46             : @CommandMetaData(
      47             :     name = "set-project-parent",
      48             :     description = "Change the project permissions are inherited from")
      49           1 : final class SetParentCommand extends SshCommand {
      50             :   @Option(
      51             :       name = "--parent",
      52             :       aliases = {"-p"},
      53             :       metaVar = "NAME",
      54             :       usage = "new parent project")
      55             :   private ProjectState newParent;
      56             : 
      57             :   @Option(
      58             :       name = "--children-of",
      59             :       metaVar = "NAME",
      60             :       usage = "parent project for which the child projects should be reparented")
      61             :   private ProjectState oldParent;
      62             : 
      63           1 :   @Option(
      64             :       name = "--exclude",
      65             :       metaVar = "NAME",
      66             :       usage = "child project of old parent project which should not be reparented")
      67             :   private List<ProjectState> excludedChildren = new ArrayList<>();
      68             : 
      69           1 :   @Argument(
      70             :       index = 0,
      71             :       required = false,
      72             :       multiValued = true,
      73             :       metaVar = "NAME",
      74             :       usage = "projects to modify")
      75             :   private List<ProjectState> children = new ArrayList<>();
      76             : 
      77             :   @Inject private ProjectCache projectCache;
      78             : 
      79             :   @Inject private ListChildProjects listChildProjects;
      80             : 
      81             :   @Inject private SetParent setParent;
      82             : 
      83             :   private Project.NameKey newParentKey;
      84             : 
      85             :   private static ParentInput parentInput(String parent) {
      86           0 :     ParentInput input = new ParentInput();
      87           0 :     input.parent = parent;
      88           0 :     return input;
      89             :   }
      90             : 
      91             :   @Override
      92             :   protected void run() throws Failure {
      93           0 :     enableGracefulStop();
      94           0 :     if (oldParent == null && children.isEmpty()) {
      95           0 :       throw die(
      96             :           "child projects have to be specified as "
      97             :               + "arguments or the --children-of option has to be set");
      98             :     }
      99           0 :     if (oldParent == null && !excludedChildren.isEmpty()) {
     100           0 :       throw die("--exclude can only be used together with --children-of");
     101             :     }
     102             : 
     103           0 :     final StringBuilder err = new StringBuilder();
     104             : 
     105           0 :     if (newParent != null) {
     106           0 :       newParentKey = newParent.getProject().getNameKey();
     107             :     }
     108             : 
     109           0 :     final List<Project.NameKey> childProjects =
     110           0 :         children.stream().map(ProjectState::getNameKey).collect(toList());
     111           0 :     if (oldParent != null) {
     112             :       try {
     113           0 :         childProjects.addAll(getChildrenForReparenting(oldParent));
     114           0 :       } catch (PermissionBackendException e) {
     115           0 :         throw new Failure(1, "permissions unavailable", e);
     116           0 :       } catch (Exception e) {
     117           0 :         throw new Failure(1, "failure in request", e);
     118           0 :       }
     119             :     }
     120             : 
     121           0 :     for (Project.NameKey nameKey : childProjects) {
     122           0 :       final String name = nameKey.get();
     123           0 :       ProjectState project = projectCache.get(nameKey).orElseThrow(illegalState(nameKey));
     124             :       try {
     125           0 :         setParent.apply(new ProjectResource(project, user), parentInput(newParentKey.get()));
     126           0 :       } catch (AuthException e) {
     127           0 :         err.append("error: insuffient access rights to change parent of '")
     128           0 :             .append(name)
     129           0 :             .append("'\n");
     130           0 :       } catch (ResourceConflictException | ResourceNotFoundException | BadRequestException e) {
     131           0 :         err.append("error: ").append(e.getMessage()).append("'\n");
     132           0 :       } catch (UnprocessableEntityException | IOException e) {
     133           0 :         throw new Failure(1, "failure in request", e);
     134           0 :       } catch (PermissionBackendException e) {
     135           0 :         throw new Failure(1, "permissions unavailable", e);
     136           0 :       }
     137           0 :     }
     138             : 
     139           0 :     if (err.length() > 0) {
     140           0 :       while (err.charAt(err.length() - 1) == '\n') {
     141           0 :         err.setLength(err.length() - 1);
     142             :       }
     143           0 :       throw die(err.toString());
     144             :     }
     145           0 :   }
     146             : 
     147             :   /**
     148             :    * Returns the children of the specified parent project that should be reparented. The returned
     149             :    * list of child projects does not contain projects that were specified to be excluded from
     150             :    * reparenting.
     151             :    */
     152             :   private List<Project.NameKey> getChildrenForReparenting(ProjectState parent) throws Exception {
     153           0 :     final List<Project.NameKey> childProjects = new ArrayList<>();
     154           0 :     final List<Project.NameKey> excluded = new ArrayList<>(excludedChildren.size());
     155           0 :     for (ProjectState excludedChild : excludedChildren) {
     156           0 :       excluded.add(excludedChild.getProject().getNameKey());
     157           0 :     }
     158           0 :     final List<Project.NameKey> automaticallyExcluded = new ArrayList<>(excludedChildren.size());
     159           0 :     if (newParentKey != null) {
     160           0 :       automaticallyExcluded.addAll(getAllParents(newParentKey));
     161             :     }
     162           0 :     for (ProjectInfo child : listChildProjects.apply(new ProjectResource(parent, user)).value()) {
     163           0 :       final Project.NameKey childName = Project.nameKey(child.name);
     164           0 :       if (!excluded.contains(childName)) {
     165           0 :         if (!automaticallyExcluded.contains(childName)) {
     166           0 :           childProjects.add(childName);
     167             :         } else {
     168           0 :           stdout.println(
     169             :               "Automatically excluded '"
     170             :                   + childName
     171             :                   + "' "
     172             :                   + "from reparenting because it is in the parent "
     173             :                   + "line of the new parent '"
     174             :                   + newParentKey
     175             :                   + "'.");
     176             :         }
     177             :       }
     178           0 :     }
     179           0 :     return childProjects;
     180             :   }
     181             : 
     182             :   private Set<Project.NameKey> getAllParents(Project.NameKey projectName) {
     183           0 :     Optional<ProjectState> ps = projectCache.get(projectName);
     184           0 :     if (!ps.isPresent()) {
     185           0 :       return Collections.emptySet();
     186             :     }
     187           0 :     return ps.get().parents().transform(ProjectState::getNameKey).toSet();
     188             :   }
     189             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750