背 景
由于业务调整或组织架构变更,原阿里云账号将不再继续使用。为了确保业务连续性和数据完整性,需要将源账号下的所有云产品及其相关数据完整、安全地迁移到目标账号。由于云效(DevOps平台)无法直接通过实例划拨至目标账号,因此需要对云效中的数据进行迁移。
创建企业
在目标账号上点击
https://devops.console.aliyun.com/organizations/standard
此链接,进入云效控制台界面,点击“新建组织”按钮。
图片
选择“云效DevOps”点击“立即开启”按钮。
图片
根据源账号云效,填写“组织名称”和“研发组织规模”,填写完成后点击“立即创建”按钮,完成云效企业的创建。
图片
新建项目
进入刚新建企业的控制台后,点击“项目协作 Projects”旁的“进入工作”按钮。
图片
点击“新建”按钮,创建项目。
图片
根据源账号项目类型,选择对应的项目模板。
图片
根据源账号项目信息进行填写,填写完成后点击“新建”按钮。
图片
新建完成后的项目如下图所示:
图片
迁移需求
源账号导出需求
在源账号需求界面,点击“批量操作”,在下拉菜单中单击“导出全部”。
图片
全选所有要导出的属性,完成后点击“开始导出”按钮。
图片
在跳转后的“批量操作记录”界面,等待“进展”的进度条显示成功后,点击右边的“下载”图表按钮,下载数据文件。
图片
需求数据表格打开后如下图所示:
图片
目标账号导入需求
在目标账号的任务界面右上角,点击“导入数据”按钮。
图片
选中“包含子工作项”后,点击“下载模板”按钮。
图片
将源账号导出的数据,根据导入模板的格式,写入到该excel表格里。
图片
注意:负责人必须为当前企业已存在的用户名称,迭代为当前项目已创建的迭代,填写不存在的信息会导致导入报错
返回到导入数据界面中,继续点击“下一步”按钮。
图片
将刚填写完数据的导入模板上传上去,完成后点击“开始导入”按钮。
图片
直到“导入工作项”显示为成功,则表示需求数据导入完成。
图片
检查需求数据是否跟源账号上的数据一致。
图片
迁移任务
源账号导出任务
在源账号任务界面,点击“批量操作”,在下拉菜单中单击“导出全部”。
图片
全选所有要导出的属性,完成后点击“开始导出”按钮。
图片
查看导出的任务数据,如下图所示:
图片
目标账号导入任务
在目标账号的任务界面右上角,点击“导入数据”按钮。
图片
选中“包含子工作项”后,点击“下载模板”按钮。
图片
将源账号导出的数据,根据导入模板的格式,写入到该excel表格里。
图片
和迁移需求一样,点击“下一步”按钮,将任务数据导入到目标账号中。
图片
迁移代码组、代码仓库、迭代
代码和迭代无法像需求和任务一样批量导出导入,为了提高效率和准确性,我们采用代码化方式批量迁移代码组、代码仓库和迭代,避免手动操作的费时费力及潜在错误。
获取组织ID
代码里需要用到“组织ID”,点击头像下拉菜单里的“管理后台”按钮,在“基本信息”页面即可看到“组织ID”。
图片
获取企业空间ID
代码里需要用到“企业空间ID”,点击
https://next.api.aliyun.com/api/devops/2021-06-25/GetCodeupOrganizatio
此链接,在“identity 企业标识”输入框内输入“组织ID”,完成后点击“发起调用”按钮。在左侧“调用结果”页面可以看到“namespaceId”,即为企业空间ID。
图片
获取个人访问令牌
点击头像,在下拉菜单中点击“个人设置”,进入个人设置界面,选择“个人访问令牌”,点击“创建访问令牌”按钮。
输入“令牌名称”,选择到期时间,按需选择权限。代码管理权限必选,后续要用它进行代码仓库数据导入操作。完成上述配置后,点击“新建”按钮创建个人访问令牌。
执行代码批量迁移
运行“源账号导出代码”,会print输出代码组、代码仓库和迭代的信息,运行“目标账号导入代码”时按代码提示输入代码组、代码仓库和迭代的信息,即可完成导入。执行前请在测试环境运行测试!!!
源账号导出代码
# -*- coding: utf-8 -*-
# This file is auto-generated, don't edit it. Thanks.
import sys,time
from typing import List
from alibabacloud_devops20210625.client import Client as devops20210625Client
from alibabacloud_tea_openapi import models as open_api_models
from alibabacloud_devops20210625 import models as devops_20210625_models
from alibabacloud_tea_util import models as util_models
from alibabacloud_tea_util.client import Client as UtilClient
ak="源账号AK"
sk="源账号SK"
organization_id='源账号组织ID'
parent_id= 源账号企业空间ID
class Sample:
def __init__(self):
pass
@staticmethod
def create_client(
access_key_id: str,
access_key_secret: str,
) -> devops20210625Client:
config = open_api_models.Config(access_key_id=ak,access_key_secret=sk)
config.endpoint = f'devops.cn-hangzhou.aliyuncs.com'
return devops20210625Client(config)
#导出代码组
@staticmethod
def ListGroupMember(
args: List[str],
) -> None:
organization_id = args[0]
groupId = args[1]
User_list=[]
client = Sample.create_client(ak, sk)
list_group_member_request = devops_20210625_models.ListGroupMemberRequest(organization_id=organization_id)
runtime = util_models.RuntimeOptions()
headers = {}
Users = client.list_group_member_with_options(groupId, list_group_member_request, headers,runtime).body.result
for User in Users:
User_dic={}
User_dic["name"]=User.name
User_dic["access_level"] = User.access_level
User_list.append(User_dic)
return User_list
@staticmethod
def ListRepositoryGroups(
args: List[str],
) -> None:
organization_id=args[0]
parent_id=args[1]
ListRepositoryGroups_list=[]
client = Sample.create_client(ak, sk)
list_repository_groups_request = devops_20210625_models.ListRepositoryGroupsRequest(organization_id=organization_id,parent_id=parent_id,page_size=100)
runtime = util_models.RuntimeOptions()
headers = {}
ListRepositoryGroups=client.list_repository_groups_with_options(list_repository_groups_request, headers, runtime).body.result
for RepositoryGroups in ListRepositoryGroups:
ListRepositoryGroup_dic = {}
ListRepositoryGroup_dic["id"] = RepositoryGroups.id
ListRepositoryGroup_dic["name"] =RepositoryGroups.name
ListRepositoryGroup_dic["description"] =RepositoryGroups.description
ListRepositoryGroup_dic["path"] = RepositoryGroups.path
ListRepositoryGroup_dic["visibility_level"] = RepositoryGroups.visibility_level
User_list=Sample.ListGroupMember([organization_id, str(RepositoryGroups.id)])
ListRepositoryGroup_dic["user_list"] = User_list
ListRepositoryGroups_list.append(ListRepositoryGroup_dic)
return ListRepositoryGroups_list
#获取代码仓库信息
@staticmethod
def GetRepositorymain(
args: List[str],
) -> None:
organization_id = args[0]
identity = args[1]
client = Sample.create_client(ak, sk)
get_repository_request = devops_20210625_models.GetRepositoryRequest(organization_id=organization_id,identity=identity)
runtime = util_models.RuntimeOptions()
headers = {}
http_url_to_repository = client.get_repository_with_options(get_repository_request, headers,runtime).body.repository.http_url_to_repository
return http_url_to_repository
@staticmethod
def ListRepositoriesmain(
args: List[str],
) -> None:
organization_id = args[0]
client = Sample.create_client(ak, sk)
list_repositories_request = devops_20210625_models.ListRepositoriesRequest(organization_id=organization_id,per_page=100)
runtime = util_models.RuntimeOptions()
headers = {}
ListRepositories = client.list_repositories_with_options(list_repositories_request, headers,runtime).body.result
repository_list = []
for Repository in ListRepositories:
repository_dic = {}
repository_dic["name"] = Repository.name
repository_dic["path"] = Repository.path
repository_dic["namespace_id"] = Repository.namespace_id
repository_dic["description"] = Repository.description
repository_dic["visibility_level"] = Repository.visibility_level
http_url_to_repository = Sample.GetRepositorymain([organization_id, Repository.id])
repository_dic["import_url"] = http_url_to_repository
repository_list.append(repository_dic)
return repository_list
# 导出迭代信息
@staticmethod
def ListProjectsmain(
args: List[str],
) -> None:
organization_id = args[0]
client = Sample.create_client(ak, sk)
list_projects_request = devops_20210625_models.ListProjectsRequest(category='Project')
runtime = util_models.RuntimeOptions()
headers = {}
projects = client.list_projects_with_options(organization_id, list_projects_request, headers,runtime).body.projects
projects_info = {}
for project in projects:
identifier = project.identifier
name = project.name
projects_info[name] = identifier
return projects_info
@staticmethod
def ListSprintsmain(
args: List[str],
) -> None:
organization_id = args[0]
space_identifier = args[1]
client = Sample.create_client(ak, sk)
list_sprints_request = devops_20210625_models.ListSprintsRequest(space_identifier=space_identifier,space_type='Project',max_results=200)
runtime = util_models.RuntimeOptions()
headers = {}
sprints = client.list_sprints_with_options(organization_id, list_sprints_request, headers,runtime).body.sprints
sprint_list = []
for sprint in sprints:
sprint_dic = {}
sprint_dic["name"] = sprint.name
sprint_dic["start_date"] = time.strftime('%Y-%m-%d', time.localtime(sprint.start_date / 1000))
sprint_dic["end_date"] = time.strftime('%Y-%m-%d', time.localtime(sprint.end_date / 1000))
sprint_list.append(sprint_dic)
return sprint_list
if __name__ == '__main__':
#获取代码组信息
ListRepositoryGroups_list=Sample.ListRepositoryGroups([organization_id,parent_id])
print("代码组信息:"+str(ListRepositoryGroups_list))
#获取代码仓库信息
repository_list = Sample.ListRepositoriesmain([organization_id, ])
print("代码仓库信息:"+str(repository_list))
#获取迭代信息
while True:
projects_info = Sample.ListProjectsmain([organization_id, ])
print("项目名称和项目ID的对应关系"+str(projects_info))
identifier = input("请输入要导出的迭代是哪个项目的ID(不需要导出请输入no):")
if identifier=="no":
break
sprint_list = Sample.ListSprintsmain([organization_id, identifier])
print("迭代的信息:"+str(sprint_list))
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
- 111.
- 112.
- 113.
- 114.
- 115.
- 116.
- 117.
- 118.
- 119.
- 120.
- 121.
- 122.
- 123.
- 124.
- 125.
- 126.
- 127.
- 128.
- 129.
- 130.
- 131.
- 132.
- 133.
- 134.
- 135.
- 136.
- 137.
- 138.
- 139.
- 140.
- 141.
- 142.
- 143.
- 144.
- 145.
- 146.
- 147.
- 148.
- 149.
- 150.
- 151.
- 152.
- 153.
目标账号导入代码
# -*- coding: utf-8 -*-
# This file is auto-generated, don't edit it. Thanks.
from typing import List
from alibabacloud_devops20210625.client import Client as devops20210625Client
from alibabacloud_tea_openapi import models as open_api_models
from alibabacloud_devops20210625 import models as devops_20210625_models
from alibabacloud_tea_util import models as util_models
from alibabacloud_tea_util.client import Client as UtilClient
import ast
ak="目标账号AK"
sk="目标账号SK"
organization_id="目标账号组织ID"
namespaceId="目标账号企业空间ID"
import_account="目标账号个人访问令牌名称"
import_token="目标账号个人访问令牌token"
class Sample:
def __init__(self):
pass
@staticmethod
def create_client(
access_key_id: str,
access_key_secret: str,
) -> devops20210625Client:
config = open_api_models.Config(access_key_id=ak,access_key_secret=sk)
config.endpoint = f'devops.cn-hangzhou.aliyuncs.com'
return devops20210625Client(config)
#导入代码组
@staticmethod
def CreateRepositoryGroupmain(
args: List[str],
) -> None:
organization_id = args[0]
name = args[1]
path = args[2]
visibility_level = args[3]
namespaceId = args[4]
description = args[5]
client = Sample.create_client(ak,sk)
create_repository_group_request = devops_20210625_models.CreateRepositoryGroupRequest(organization_id=organization_id,name=name,path=path,visibility_level=visibility_level,parent_id=namespaceId,descriptinotallow=description)
runtime = util_models.RuntimeOptions()
headers = {}
print(client.create_repository_group_with_options(create_repository_group_request, headers, runtime))
#获取源代码组ID与目标代码组ID的对应关系
@staticmethod
def ListRepositoryGroups(
args: List[str],
) -> None:
organization_id = args[0]
parent_id = args[1]
ListRepositoryGroups_list = []
client = Sample.create_client(ak,sk)
list_repository_groups_request = devops_20210625_models.ListRepositoryGroupsRequest(organization_id=organization_id,parent_id=parent_id,page_size=100)
runtime = util_models.RuntimeOptions()
headers = {}
ListRepositoryGroups = client.list_repository_groups_with_options(list_repository_groups_request, headers,runtime).body.result
for RepositoryGroups in ListRepositoryGroups:
ListRepositoryGroup_dic = {}
ListRepositoryGroup_dic["id"] = RepositoryGroups.id
ListRepositoryGroup_dic["name"] = RepositoryGroups.name
ListRepositoryGroup_dic["description"] = RepositoryGroups.description
ListRepositoryGroup_dic["path"] = RepositoryGroups.path
ListRepositoryGroup_dic["visibility_level"] = RepositoryGroups.visibility_level
ListRepositoryGroups_list.append(ListRepositoryGroup_dic)
return ListRepositoryGroups_list
#导入代码仓库
@staticmethod
def CreateRepositorymain(
args: List[str],
) -> None:
organization_id = args[0]
name = args[1]
path = args[2]
namespace_id = args[3]
description = args[4]
visibility_level = args[5]
import_url = args[6]
import_account = args[7]
import_token = args[8]
client = Sample.create_client(ak,sk)
create_repository_request = devops_20210625_models.CreateRepositoryRequest(organization_id=organization_id,name=name,path=path,namespace_id=namespace_id,descriptinotallow=description,visibility_level=visibility_level,import_url=import_url,import_account=import_account,import_token=import_token)
runtime = util_models.RuntimeOptions()
headers = {}
print(client.create_repository_with_options(create_repository_request, headers, runtime))
# 得到项目名称和项目ID的对应关系
@staticmethod
def ListProjectsmain(
args: List[str],
) -> None:
organization_id = args[0]
client = Sample.create_client(ak,sk)
list_projects_request = devops_20210625_models.ListProjectsRequest(category='Project')
runtime = util_models.RuntimeOptions()
headers = {}
projects = client.list_projects_with_options(organization_id, list_projects_request, headers,runtime).body.projects
projects_info = {}
for project in projects:
identifier = project.identifier
name = project.name
projects_info[name] = identifier
return projects_info
#创建迭代
@staticmethod
def CreateSprintmain(
args: List[str],
) -> None:
organization_id=args[0]
identifier=args[1]
FROM_diedai=args[2]
staff_id=args[3]
client = Sample.create_client(ak,sk)
create_sprint_request = devops_20210625_models.CreateSprintRequest(
staff_ids=[staff_id], #ram用户uid
name=FROM_diedai["name"], #迭代名称
space_identifier=identifier, #项目ID
start_date=FROM_diedai["start_date"], #迭代开始时间
end_date=FROM_diedai["end_date"] #迭代结束时间
)
runtime = util_models.RuntimeOptions()
headers = {}
print(client.create_sprint_with_options(organization_id, create_sprint_request, headers, runtime))
if __name__ == '__main__':
# 导入代码组
FROMListRepositoryGroups_list=ast.literal_eval(input("请输入源账号导出的代码组列表:"))
for RepositoryGroup in FROMListRepositoryGroups_list:
Sample.CreateRepositoryGroupmain([organization_id,RepositoryGroup["name"],RepositoryGroup["path"],RepositoryGroup["visibility_level"],namespaceId,RepositoryGroup["description"]])
# 获取源代码组ID与目标代码组ID的对应关系
TOListRepositoryGroups_list = Sample.ListRepositoryGroups([organization_id, namespaceId])
FROM_TOListRepositoryGroupID_dic = {}
for FROMListRepositoryGroup in FROMListRepositoryGroups_list:
for TOListRepositoryGroup in TOListRepositoryGroups_list:
if FROMListRepositoryGroup["name"] == TOListRepositoryGroup["name"]:
FROM_TOListRepositoryGroupID_dic[FROMListRepositoryGroup["id"]] = TOListRepositoryGroup["id"]
FROMparent_id=ast.literal_eval(input("请输入源账号的parent_id,也就是namespace_id:"))
FROM_TOListRepositoryGroupID_dic[FROMparent_id]=int(namespaceId)
print("源账号代码组ID与目标账号代码组ID的对应关系:"+str(FROM_TOListRepositoryGroupID_dic))
#导入代码仓库
FROMrepository_list=ast.literal_eval(input("请输入源账号导出的代码仓库列表:"))
for FROMrepository in FROMrepository_list:
Sample.CreateRepositorymain([organization_id, FROMrepository["name"], FROMrepository["path"],FROM_TOListRepositoryGroupID_dic[FROMrepository["namespace_id"]], FROMrepository["description"],FROMrepository["visibility_level"], FROMrepository["import_url"], import_account, import_token])
# 导入迭代
while True:
projects_info = Sample.ListProjectsmain([organization_id, ])
print("项目名称和项目ID的对应关系:"+str(projects_info))
identifier = input("请输入要导入的迭代是哪个项目的ID(不需要导出请输入no):")
if identifier=="no":
break
print("用户ID:{用户A:用户A的RAM账号UID,用户B:用户B的RAM账号UID,用户C:用户C的RAM账号UID}")
print("项目与负责人的对应关系:{'项目A':'负责人A','项目B':'负责人B','项目C':'负责人C'}")
staff_id = input("请输入要导入迭代负责人的RAM账号UID:")
FROM_diedai_list = ast.literal_eval(input("请输入要导入的迭代内容列表:"))
for FROM_diedai in FROM_diedai_list:
Sample.CreateSprintmain([organization_id, identifier, FROM_diedai, staff_id])
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
- 111.
- 112.
- 113.
- 114.
- 115.
- 116.
- 117.
- 118.
- 119.
- 120.
- 121.
- 122.
- 123.
- 124.
- 125.
- 126.
- 127.
- 128.
- 129.
- 130.
- 131.
- 132.
- 133.
- 134.
- 135.
- 136.
- 137.
- 138.
- 139.
- 140.
- 141.
- 142.
- 143.
- 144.
- 145.
- 146.
- 147.
- 148.
- 149.
- 150.
- 151.
- 152.
总 结
为了确保业务连续性和数据完整性,针对源阿里云账号即将停用的情况,我们完成了云效(DevOps平台)的数据迁移工作。由于云效无法直接通过实例迁移至目标账号,我们采取了数据导出与导入的方式,确保所有项目、流水线配置及代码仓库等数据完整无误地迁移到目标账号下。至此,平稳顺利的完成此次迁移。