云效跨賬號遷移,你學會了嗎?
背 景
由于業務調整或組織架構變更,原阿里云賬號將不再繼續使用。為了確保業務連續性和數據完整性,需要將源賬號下的所有云產品及其相關數據完整、安全地遷移到目標賬號。由于云效(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))
目標賬號導入代碼
# -*- 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])
總 結
為了確保業務連續性和數據完整性,針對源阿里云賬號即將停用的情況,我們完成了云效(DevOps平臺)的數據遷移工作。由于云效無法直接通過實例遷移至目標賬號,我們采取了數據導出與導入的方式,確保所有項目、流水線配置及代碼倉庫等數據完整無誤地遷移到目標賬號下。至此,平穩順利的完成此次遷移。