Skip to main content

Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.

Jump to: navigation, search

Difference between revisions of "Hudson-ci/features/Team Concept"

(Team member)
(Future Enhancement requests)
 
(9 intermediate revisions by 2 users not shown)
Line 27: Line 27:
 
*Create, configure and delete a slave
 
*Create, configure and delete a slave
  
=== Authorization&nbsp; Schemes<br>  ===
+
=== Authorization Schemes<br>  ===
  
 
The are three authorization schemes  
 
The are three authorization schemes  
Line 35: Line 35:
 
*Team Member Authorization<br>
 
*Team Member Authorization<br>
  
=== Job scope ===
+
=== Team Item (Job, View, Slave) ===
  
There are two scopes <br>
+
Team Items are entities that are scoped to a team. Currently Jobs, Views, Slaves can be created in this scope. The team item from a team is not visible to another team.
  
'''Public Scope'''<br>
+
There are two scopes 
  
*Any one can view this job <br>
+
'''Public Scope'''
*Only Sys Admin can delete this job<br>
+
*Only Sys Admin can configure this job<br>
+
*Only Sys Admin can run this job<br>
+
  
'''Team Scope'''<br>
+
They all have public read permission. In this scope
  
*Only team members can view team private jobs
+
*Any one can view the items 
*Only certain team members can manually run
+
* Only Sys Admin can create items 
*Only certain team members can edit configuration
+
*Only Sys Admin can delete items 
*Only certain team members can delete job
+
*Only Sys Admin can configure the items
*Only certain team members can delete job build<br>
+
*Only Sys Admin can run the jobs in this scope
  
=== Job Visibility<br>  ===
+
'''Team Scope'''
  
The visibility of a job can be set to<br>
+
When a team member with appropriate permission create items (Jobs, Views, Slaves), then they are created with team scope
  
'''public '''<br>
+
*Only team members can view team private items
 +
*Only certain team members with build permission can manually build the jobs
 +
*Only certain team members with appropriate configure permission can configure the items
 +
*Only certain team members with appropriate delete permission can delete items
  
*Any one can view this job<br>
+
=== Team Item Visibility  ===
*Only members can configure, run and delete the job<br>
+
  
'''Team private'''<br>
+
The visibility of a team item (job, view, slave) can be set to
  
*Only visible to team members<br>
+
'''public '''
  
Particular team can only view their jobs and any public jobs.&nbsp; Anonymous users can view jobs only allowed to view as public <br>
+
*Any one can view this item
 +
*Only team members with appropriate permission can configure or delete the item
 +
*Only team members with build permission can build the job
  
&nbsp;
+
'''Team private'''
 +
 
 +
*Only visible to team members
 +
 
 +
Particular team can only view their jobs, any public jobs and any jobs other teams that are set to visible to this team
  
 
== Capabilities  ==
 
== Capabilities  ==
Line 85: Line 90:
 
| -
 
| -
 
|-
 
|-
| Create team admin  
+
| Create and configure team admin  
 
| Y  
 
| Y  
 
| Team  
 
| Team  
 
| -
 
| -
 
|-
 
|-
| Create team member  
+
| Create and configure team member  
 
| Y  
 
| Y  
 
| Team  
 
| Team  
Line 108: Line 113:
 
| Y  
 
| Y  
 
| Team  
 
| Team  
| Self-created or &lt;Team&gt;
+
| &lt;Team&gt;
 
|-
 
|-
 
| Configure team job  
 
| Configure team job  
 
| Y  
 
| Y  
 
| Team  
 
| Team  
| Self-created or &lt;Team&gt;
+
| &lt;Team&gt;
 
|-
 
|-
 
| Run team job  
 
| Run team job  
 
| Y  
 
| Y  
 
| Team  
 
| Team  
| Self-created or &lt;Team&gt;
+
| &lt;Team&gt;
 
|-
 
|-
| Set team job public
+
| Set team job visibility
 
| Y  
 
| Y  
 
| Team  
 
| Team  
| &lt;Team&gt;
+
| -
 +
|-
 +
| Create team View
 +
| Y
 +
| Team  
 +
| <team>
 +
|-
 +
| Configure team View
 +
| Y
 +
| Team
 +
| <team>
 +
|-
 +
| Delete team View
 +
| Y
 +
| Team
 +
| <team>
 +
|-
 +
| Set team View visibility
 +
| Y
 +
| Team
 +
| -
 +
|-
 +
| Create team Slave
 +
| Y
 +
| Team
 +
| <team>
 +
|-
 +
| Configure team Slave
 +
| Y
 +
| Team
 +
| <team>
 +
|-
 +
| Delete team Slave
 +
| Y
 +
| Team
 +
| <team>
 +
|-
 +
| Set team Slave visibility
 +
| Y
 +
| Team
 +
| -
 
|}
 
|}
  
Line 144: Line 189:
 
| &lt;Team&gt;  
 
| &lt;Team&gt;  
 
| Only within the same team if capability granted by system or team admin
 
| Only within the same team if capability granted by system or team admin
|-
 
| Self-created
 
| Only if created by that team member
 
 
|}
 
|}
  
 
== Requirements  ==
 
== Requirements  ==
  
*Team concept must be implemnted as an Authorization scheme and easily switchable to other authorization scheme  
+
*Team concept must be implemented as an Authorization scheme and easily switchable to other authorization scheme  
 
*Multiple teams must be able to use same name for a job.  
 
*Multiple teams must be able to use same name for a job.  
 
*Jobs must be saved in team specific folders  
 
*Jobs must be saved in team specific folders  
Line 162: Line 204:
 
== Desirable Features  ==
 
== Desirable Features  ==
  
*It would be good if team configurations were preserved even when team feature is not active. Currently, toggling security or switching security realms or authorization methods does not preserve previous configuration data. However, the investment in team configuration is liable to be substantial and responsibility divided among multiple people; it could be very difficult to recover manually.<br>
+
*It would be good if team configurations were preserved even when team feature is not active. Currently, toggling security or switching security realms or authorization methods does not preserve previous configuration data. However, the investment in team configuration is liable to be substantial and responsibility divided among multiple people; it could be very difficult to recover manually.
 +
 
 +
Note: This feature is implemented.
  
 
== Implementation Note ==
 
== Implementation Note ==
Line 423: Line 467:
  
 
== Future Enhancement requests ==
 
== Future Enhancement requests ==
 
* Isolate slave per team. Allow Team Admin to create slaves that can be used only to execute execute jobs from that specific team.
 
 
* All tabs View exist for everyone. Provide functionality so that Views (tabs) could be associated with teams.  Only team admins have privileges to modify  views. Currently, since views are not an entity that can be assigned to a particular team (only jobs are) the access to views cannot be managed on a team base.
 
  
 
* A sysadmin sees all jobs. This basically means that you need a seperate sysadmin login for anyone that needs to manage teams. Either that, or a sysadmin does not benefit from the team concept. For example, I am both sysadmin and a user of hudson. I don't want to see other team's jobs, so I had to have IT create an LDAP service account so that I have a seperate sysadmin login. They don't really want to have to do this for the 4 other users who also should be sysadmins. In other enterprise tools I use, this is handled by temporary elevation of access while logged in (see JIRA,Confluence, etc.), AKA 'websudo'.
 
* A sysadmin sees all jobs. This basically means that you need a seperate sysadmin login for anyone that needs to manage teams. Either that, or a sysadmin does not benefit from the team concept. For example, I am both sysadmin and a user of hudson. I don't want to see other team's jobs, so I had to have IT create an LDAP service account so that I have a seperate sysadmin login. They don't really want to have to do this for the 4 other users who also should be sysadmins. In other enterprise tools I use, this is handled by temporary elevation of access while logged in (see JIRA,Confluence, etc.), AKA 'websudo'.
  
* There is no bulk way to migrate sets of jobs to a team. (for instance, by regex or wildcard.) I had to click 600 checkboxes to migrate my jobs. I originally started to script it by modifying the XML but then realized there was so much to track because of job renaming etc. that I gave up because I would have spent more time debugging and fixing things than just brute force migration.
+
* There is no bulk way to migrate sets of jobs to a team. (for instance, by regex or wildcard.) I had to click 600 checkboxes to migrate my jobs. I originally started to script it by modifying the XML but then realized there was so much to track because of job renaming etc. that I gave up because I would have spent more time debugging and fixing things than just brute force migration. Update: This is implemented by the [https://github.com/hudson3-plugins/copy-team-cli-plugin copy-team-cli-plugin].

Latest revision as of 17:49, 11 June 2014

The concept of Team Management in Hudson is to implement team based authorization so that different authorization scheme can be assigned to each team. This helps multiple software project teams to use a single Hudson, but each team will have its own isolation of jobs, views and slaves. See also the Team Management User view page.

Concept

System admin

Every Hudson instance will have at least one Sys Admin. Each Sys Admin will have Hudson wide authorization. Some of the main responsibilities are

  • Set up team and adding a team admin
  • All  higher level administrations like installing plugin, setting up security, and configuring the system

Team Admin

Every team will have at least one team admin . Team admin has less power. Each Team Admin will have team wided authorization.  Some of the main responsibilities are

  • Add and remove team members
  • Setting permission for team members
  • Can allow a job to be viewed by other team or public
  • Can allow other teams or public to view a job configuration

Team member

Each team will have one or more members. The authorizations allowed for each member are

  • Create, configure, delete or build a job
  • Create, configure and delete a view
  • Create, configure and delete a slave

Authorization Schemes

The are three authorization schemes

  • System Admin Authorization
  • Team Admin Authorization
  • Team Member Authorization

Team Item (Job, View, Slave)

Team Items are entities that are scoped to a team. Currently Jobs, Views, Slaves can be created in this scope. The team item from a team is not visible to another team.

There are two scopes

Public Scope

They all have public read permission. In this scope

  • Any one can view the items
  • Only Sys Admin can create items
  • Only Sys Admin can delete items
  • Only Sys Admin can configure the items
  • Only Sys Admin can run the jobs in this scope

Team Scope

When a team member with appropriate permission create items (Jobs, Views, Slaves), then they are created with team scope

  • Only team members can view team private items
  • Only certain team members with build permission can manually build the jobs
  • Only certain team members with appropriate configure permission can configure the items
  • Only certain team members with appropriate delete permission can delete items

Team Item Visibility

The visibility of a team item (job, view, slave) can be set to

public

  • Any one can view this item
  • Only team members with appropriate permission can configure or delete the item
  • Only team members with build permission can build the job

Team private

  • Only visible to team members

Particular team can only view their jobs, any public jobs and any jobs other teams that are set to visible to this team

Capabilities

Capability System Admin Team Admin Team Member
Create system admin Y - -
Create and configure team admin Y Team -
Create and configure team member Y Team -
View team job Y Team Team
Create team job Y Team <Team>
Delete team job Y Team <Team>
Configure team job Y Team <Team>
Run team job Y Team <Team>
Set team job visibility Y Team -
Create team View Y Team <team>
Configure team View Y Team <team>
Delete team View Y Team <team>
Set team View visibility Y Team -
Create team Slave Y Team <team>
Configure team Slave Y Team <team>
Delete team Slave Y Team <team>
Set team Slave visibility Y Team -

Legend:

Scope Means
Y All teams
- Not allowed
Team Only within the same team
<Team> Only within the same team if capability granted by system or team admin

Requirements

  • Team concept must be implemented as an Authorization scheme and easily switchable to other authorization scheme
  • Multiple teams must be able to use same name for a job.
  • Jobs must be saved in team specific folders
  • Build History should show only the jobs accessible to the current user
  • The people dashboard should display only the team members of the current user
  • Build executor status must display jobs of the current user team
  • If a team is deleted, then all its jobs must become global jobs, so that System Admin can assign them to other teams
  • When switching between Team administration to/from standard or no administration data (jobs) must not be lost

Desirable Features

  • It would be good if team configurations were preserved even when team feature is not active. Currently, toggling security or switching security realms or authorization methods does not preserve previous configuration data. However, the investment in team configuration is liable to be substantial and responsibility divided among multiple people; it could be very difficult to recover manually.

Note: This feature is implemented.

Implementation Note

In order to support jobs with same name on multiple teams, all jobs are associated with an id. The is a fully qualified name "team.jobName" for team jobs or "jobName" for public (non-team or team sysAdmin) jobs. So the URL would look like the following

TeamJobAcessUrl.png

When the Team Manager is enabled, all job names used in Command Line, must have the fully qualified Name (Eg. myTeam.myJob) for team jobs or unqualified name (e.g., myJob) for public jobs.

Implementation Update

Use of may in the following indicates a proposal is on the table but no final decision has been made.

No job id

The job id has been removed. Job id and team id no longer appear in a job's config file.

Job name

The job name of a team job (except for the public team) is team-name.job-part, where team-name is the name of the team and job-part is the part of the job name that is unique within the team. For a public job, the team-name. is omitted; only the job-part is used. The entire job name must, of course, be globally unique.

The '.' (dot/period) used to separate the parts of a team job name is not a reserved character. It may appear in the names of public jobs and elsewhere in the names of team jobs. Dot is chosen because it is a conventional namespace separator character. However, the presence of a dot does not, of itself, identify the job as belonging to a namespace. Only the creation of a job in a specific team does that; the name follows the membership, not vice versa.

The only two places in the UI where the job name appears as job-part without the team qualifier are in the New Job and Configure Job pages. Both of these are edit contexts; in the latter, an edit causes a job rename.

To minimize confusion, a check may be added to New Job to discourage names with extra dots, but that would restrict the user while not preventing the creation of such jobs by other means.

Job Location

Public jobs are folders in HUDSON_HOME/jobs/job-name. Team jobs are folders in HUDSON_HOME/teams/team-name/job-name. This addresses a user requirement that files for a particular team be isolated to a single folder.

There may be an option to co-locate a team job with public jobs, in HUDSON_HOME/jobs/job-name. The option would be set when a job is created. There may be a way to change the default of this option in the Hudson system configuration. Turning co-locate on makes it more likely that plugins that examine job files or back them up continue to work without modification. Turning it off satisfies the separate folder requirement, which not all users may have. The option would pave the way for entirely user-configurable individual job location, not contemplated in this release.


TeamManager

Formerly TeamManager was only available when team was enabled. To allow team jobs to be visible and usable even if team is temporarily disabled - a user requirement - the TeamManager will be available and active at all times, independent of whether team validation is in use. If team validation is disabled, it will behave as if all users are members of the public team. Otherwise, it will assign users to their correct team(s).

You may be wondering how, if the team namespace qualification in a job name is not reserved and team jobs might appear in different locations depending on configuration, how are team jobs located? When any job is created, it is assigned to the current user's team. When a job is loaded or saved, Hudson asks the TeamManager and uses the location assigned to the team.

There are two exceptions to this rule:

  • A user may be a member of more than one team.
  • The sys admin user is considered to be a member of all teams, including the public team.

To deal with this, two changes may be required:

  • The New Job page may have an option to select a team in either of these cases.
  • The CLI commands dealing with job management may have an added, optional argument to designate a target team.

Public Means Public Visibility

Any user, even the anonymous user, can see public jobs, their build status and history, build logs, etc. Anonymous users, however, cannot configure the jobs or start a build.

If you don't like this behavior and want to keep all team jobs private, see following section, Shared Teams.

Shared Teams and Roles

The team authorization strategy respects roles.

To create a shared team, create a team, e.g., named Shared, and make all users members of the shared team with only read access. If the Shared team has no admins, only the sys admin will be able to manage jobs in Shared. This is equivalent to the public team but only visible to team members or admins.

This scheme can be generalized to multiple groups of teams; each group can have their own shared group which is not visible to other groups.

Obviously, administration as described above would be tedious. It is easier to use a security manager that allows you to define roles, like LDAP. Then all team users can be assigned a "shared" role, and only the role needs to be added to a shared team folder.

Note that with the built-in Hudson security manager, all logged in users have the Authority/role "authenticated".

list-teams CLI Command

The list-teams CLI Command allows a command line user to discover the team access permissions of the user or of users in teams the user administers. No user has access to more information than is provided by the Hudson UI, except that an individual user who is not an admin may not be aware of all the details.

java -jar <path-to>/hudson-cli.jar list-teams [--username <username> --password <password>] 
                                              [-u <user-list>] [-format plain|xml|csv]

The --username and --password can be specified on the command, in a preceding login command or in any of the ways that username and password can be passed to simple HTML authentication. If there is no identified user, the response is for the anonymous user.

The -u or --users option allows an admin user to request information about users in teams the logged in user administers. The system administrator can ask about any or all team users. The user-list may be specified as a comma-separated list of user names, e.g. "bill" or "bart,betty". "*" includes all administered team users, including the current user. If any of the names in the list is not an administered user, the command fails.

The -format option allows selection of xml, csv or plain report format. The default is plain. (-format is used rather than --format for compatibility with the list-changes command.)

For example, list-teams with no arguments always produces:

$ ... list-teams
public  Read

Because even the anonymous user can see public jobs and build information.

$ ... list-teams --username bart --password secret
A       Build Configure Create Delete ExtendedRead Read WipeOut Workspace
B       Admin Build Configure Create Delete ExtendedRead Read WipeOut Workspace
public  Read

Shows that bart is a member of two teams with all permissions, except bart is admin only in team B. Like all users, bart has Read access to public team jobs.

The same result in columnar, csv format:

$ ... list-teams --username bart --password secret -format csv
Team,Admin,Build,Configure,Create,Delete,ExtendedRead,Read,WipeOut,Workspace
A,-,X,X,X,X,X,X,X
B,X,X,X,X,X,X,X,X
public,-,-,-,-,-,-,X,-

Finally, the result for users administered by bart, in all three formats. Plain:

$ ... list-teams --username bart --password bart -u "*"
bart B  Admin Build Configure Create Delete ExtendedRead Read WipeOut Workspace
biff B  Build Configure Create ExtendedRead Read Workspace
bill B  Build Configure Create Delete ExtendedRead Read WipeOut Workspace

CSV:

$ ... list-teams --username bart --password secret -format csv -u "*"
User,Team,Admin,Build,Configure,Create,Delete,ExtendedRead,Read,WipeOut,Workspace
bart,B,X,X,X,X,X,X,X,X
biff,B,-,X,X,X,-,X,X,-
bill,B,-,X,X,X,X,X,X,X

And the ever-tedious XML:

$ ... list-teams --username bart --password bart -format xml -u "*"
<users>
  <user>
    <name>bart</name>
    <teams>
      <team>
        <name>B</name>
        <permissions>
          <permission>Admin</permission>
          <permission>Build</permission>
          <permission>Configure</permission>
          <permission>Create</permission>
          <permission>Delete</permission>
          <permission>ExtendedRead</permission>
          <permission>Read</permission>
          <permission>WipeOut</permission>
          <permission>Workspace</permission>
        </permissions>
      </team>
    </teams>
  </user>
  <user>
    <name>biff</name>
    <teams>
      <team>
        <name>B</name>
        <permissions>
          <permission>Build</permission>
          <permission>Configure</permission>
          <permission>Create</permission>
          <permission>ExtendedRead</permission>
          <permission>Read</permission>
          <permission>Workspace</permission>
        </permissions>
      </team>
    </teams>
  </user>
  <user>
    <name>bill</name>
    <teams>
      <team>
        <name>B</name>
        <permissions>
          <permission>Build</permission>
          <permission>Configure</permission>
          <permission>Create</permission>
          <permission>Delete</permission>
          <permission>ExtendedRead</permission>
          <permission>Read</permission>
          <permission>WipeOut</permission>
          <permission>Workspace</permission>
        </permissions>
      </team>
    </teams>
  </user>
</users>

Since bart is admin only in team B, only user information about team B is shown. A system admin, however, can see all users in all teams.

$ ... list-teams --username admin --password supersecret -u "*" -format csv
User,Team,Admin,Build,Configure,Create,Delete,ExtendedRead,Read,WipeOut,Workspace
bart,A,-,X,X,X,X,X,X,X
bart,B,X,X,X,X,X,X,X,X
bart,public,-,-,-,-,-,-,X,-
biff,A,-,X,-,-,-,-,X,-
biff,public,-,-,-,-,-,-,X,-
bill,A,X,X,X,X,X,X,X,X
bill,B,-,X,X,X,X,X,X,X
bill,public,-,-,-,-,-,-,X,-

create-team CLI Command

The create-team CLI Command allows a command line user to create a new team. The user must be a sys admin.

java -jar <path-to>/hudson-cli.jar create-team <teamname> [--username <username> --password <password>] 

The --username and --password can be specified on the command, in a preceding login command or in any of the ways that username and password can be passed to simple HTML authentication. If there is no identified user, the response is for the anonymous user.

list-jobs CLI command

The list-jobs CLI Command allows a command line user to discover names of jobs in Hudson. Only job names for which the user has READ permission are shown.

java -jar <path-to>/hudson-cli.jar list-jobs [<teamname>] [--username <username> --password <password>] 
                                              [-format plain|xml|csv]

If teamname is not specified, the jobs for which the current user has READ permission are listed. If teamname is specified and the team exists and the current user has READ access, only the jobs from the team are listed.

The plain format (default) and csv format are equivalent, as the output has only a single job name per line.

The --username and --password can be specified on the command, in a preceding login command or in any of the ways that username and password can be passed to simple HTML authentication. If there is no identified user, the response is for the anonymous user.

Single Team Visibility

The team concept design provides a flexible way for multiple teams to interact in a controlled way within a single Hudson instance. Taking it one step further, it is possible to confine each user to a single team with no visibility to any other teams. This is easy enough to do statically with the standard team configuration UI, or by dynamically by implementing a TeamAwareSecurityRealm, which is a hook for an external source to control team membership and permissions.

TeamAwareSecurityRealm

TeamAwareSecurityRealm is an abstract class that provides a simple API to control user access to teams. Potential implementors of this hook should be aware it was designed to address a specific need: a no-admin Hudson in which user access to (a single team at a time) is controlled outside Hudson. It was not designed for the case where team membership is (also) configured in teams.xml or for any combination of team configuration inside Hudson and team aware security realm configuration outside Hudson, and there would probably be issues if this were attempted.

The primary advantage of the TeamAwareSecurityRealm is it can allow each user to see only one team at a time, but which team the user sees can be changed dynamically, e.g., by following particular links in a user interface outside Hudson. How this is implemented is entirely up to the security realm.

Team GetCurrentUserTeam()

This returns a single Team. (The security realm has access to the Team Manager, so it can determine the Team object from the team name.) The security realm is aware of the current user's identity.

boolean isCurrentUserSysAdmin()

Returns true if the current user is a system administrator for teams. Default false. The system administrator can add and remove teams, etc., using the team administration UI.

boolean isCurrentUserTeamAdmin()

Returns true if the current user is team administrator for the team returned by GetCurrentUserTeam. Default false. The team administrator can add and remove team members using the team administration UI, and has all privileges of a team member.

List<Permission> getCurrentUserPermissions

This is not currently in 3.1.0. Return a list of permissions for the current user. If an empty list is returned, all permissions are granted. Default is empty list. Otherwise, the list may contain one or more of Item.CREATE, Item.DELETE, Item.CONFIGURE and Item.BUILD. The TeamAwareSecurityRealm can use these to allow some users to have, e.g., only read-only access to a team.

Show Unqualified Job Name

This is not currently in 3.1.0. When team is active, all team job names are qualified by team name. This allows users to have visibility to multiple teams and to the public team without job name synonyms. However, in an environment were each user has access to only one team (at a time), the team identifiers in the job name are redundant, except to distinguish public jobs from team jobs.

The Show unqualified job names option in Team security configuration tells Hudson to display job names, as much as possible, without team qualifiers. (Since the qualified job names are the actual names of the jobs, Hudson can't prevent plugins from displaying qualified names, though it will provide a mechanism for them to do so, if they wish.)

The specific pages controlled by this option are:

  • Jobs Status
  • TBD

Team White Paper (Work In Progress)

See

http://www.eclipse.org/forums/index.php/mv/msg/635685/1229482/#msg_1229482

White Paper

Future Enhancement requests

  • A sysadmin sees all jobs. This basically means that you need a seperate sysadmin login for anyone that needs to manage teams. Either that, or a sysadmin does not benefit from the team concept. For example, I am both sysadmin and a user of hudson. I don't want to see other team's jobs, so I had to have IT create an LDAP service account so that I have a seperate sysadmin login. They don't really want to have to do this for the 4 other users who also should be sysadmins. In other enterprise tools I use, this is handled by temporary elevation of access while logged in (see JIRA,Confluence, etc.), AKA 'websudo'.
  • There is no bulk way to migrate sets of jobs to a team. (for instance, by regex or wildcard.) I had to click 600 checkboxes to migrate my jobs. I originally started to script it by modifying the XML but then realized there was so much to track because of job renaming etc. that I gave up because I would have spent more time debugging and fixing things than just brute force migration. Update: This is implemented by the copy-team-cli-plugin.

Back to the top