Миграция будет проходить по статье https://confluence.atlassian.com/adminjiraserver/migrating-from-jira-cloud-to-data-center-applications-938846963.html
Запускаем бэкап. Обращаем внимание что начиная с 30 ноября 2024, такой простоты не будет и придется мучить csv.
Скачиваем бэкап на развернутую jiradc по пути /var/atlassian/application-data/shared-home/import
Он оказался размером 140GB, поэтому обращаем внимание на следующее:
If your backup is 2GB or more, import the attachments separately
Это говорит о том что нужно сделать
unzip backup.zip
Затем
zip newbackup.zip entities.xml activeobjects.xml
А папку attachments из папки data отправить по пути home-directory/data/attachments
mv attachments ../../../data
После развертывания бэкапа столнулся с тем что не открывался workflow с ошибкой
Error parsing workflow XML: Attribute "ruleKey" must be declared for element type "validator". Please see log for more details.
Решение описано здесь: https://confluence.atlassian.com/jirakb/attribute-rulekey-must-be-declared-for-element-type-validator-1354203361.html чтож нужно просто пересохранить wf в клауде и проделать все предыдущие операции заново.
Прогулявшись по админке увидел что не открываются кастомфилды. Посмотрел логи и увидел сдледующее:
Caused by: java.lang.IllegalStateException: Duplicate key customfield_10066 (attempted merging values com.atlassian.jira.issue.fields.screen.FieldScreenLayoutItemImpl@6a1ada31 and com.atlassian.jira.issue.fields.screen.FieldScreenLayoutItemImpl@6a1add97)
Точно помню что уже решал такую проблему, но на момент текущего подхода на странице атласовской доки с bulk resolution было 504 поэтому пришлось доставать из вэбархива. Так же добавил эту статью себе с переводом. Если коротко то все решается вот таким запросом:
delete from fieldscreenlayoutitem where id in (select max(i.id) as deleteme
from fieldscreen f, fieldscreenlayoutitem i, fieldscreentab t
where f.id = t.fieldscreen
and i.fieldscreentab = t.id
group by f.name, i.fieldidentifier having count(*) > 1);
Ставим Project Configurator и при попытке экспортировать проект ловим такую ошибку:
Some errors occurred:
Found custom field with values for issues in exported project(s), with id: 10084, name: Issue color and type: com.pyxis.greenhopper.jira:jsw-issue-color. This field does not exist. Check if its type is defined in a plugin that has been disabled.
Found custom field with values for issues in exported project(s), with id: 10013, name: [CHART] Time in Status and type: com.atlassian.jira.ext.charting:timeinstatus. This field does not exist. Check if its type is defined in a plugin that has been disabled.
Решается так:
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.ofbiz.OfBizDelegator
import groovy.sql.GroovyRowResult
import groovy.sql.Sql
import org.ofbiz.core.entity.ConnectionFactory
import org.ofbiz.core.entity.DelegatorInterface
import java.sql.Connection
//select("""select * from customfieldvalue where customfield = 10013;""")
//update("""DELETE FROM customfieldvalue WHERE customfield = 10013;""")
//select("""select * from customfieldvalue where customfield = 10084;""")
//update("""DELETE FROM customfieldvalue WHERE customfield = 10084;""")
//select("""select * from customfield where id = 10013;""")
//update("""DELETE FROM customfield where id = 10013;""")
//select("""select * from customfield where id = 10084;""")
update("""DELETE FROM customfield where id = 10084;""")
//update("delete/insert ...")
def select(String query) {
OfBizDelegator delegator = ComponentAccessor.getOfBizDelegator();
DelegatorInterface delegatorInterface = delegator.getDelegatorInterface();
String helperName = delegatorInterface.getGroupHelperName("default");
Connection connection = ConnectionFactory.getConnection(helperName);
Sql sql = new Sql(connection);
List<GroovyRowResult> resultRows = []
try {
resultRows.addAll(sql.rows(query));
} finally {
//sql.close()
connection.close()
}
return resultRows
}
def update(String query) {
OfBizDelegator delegator = ComponentAccessor.getOfBizDelegator();
DelegatorInterface delegatorInterface = delegator.getDelegatorInterface();
String helperName = delegatorInterface.getGroupHelperName("default");
Connection connection = ConnectionFactory.getConnection(helperName);
Sql sql = new Sql(connection);
def numberOfUpdated
try {
numberOfUpdated = sql.executeUpdate(query);
} finally {
//sql.close()
connection.close()
}
return numberOfUpdated
}
И сразу пофиксим
[In [Resolution Declined] ] Found two items with the same external identification 'Declined' and with ids: 10006 and 10005
Простым удалением из списка резолюции с более поздним ID
После этого проект успешно экспортировался но при импорте наблюдается следующая ошибка:
com.atlassian.jira.exception.ParseException: The entityId of entityName '835006' is missing at com.atlassian.jira.imports.project.parser.EntityPropertyParserImpl.verifyRequiredAttribute(EntityPropertyParserImpl.java:43) at com.atlassian.jira.imports.project.parser.EntityPropertyParserImpl.parse(EntityPropertyParserImpl.java:28) at com.atlassian.jira.imports.project.handler.IssueRelatedEntitiesPartitionHandler.handleEntityProperty(IssueRelatedEntitiesPartitionHandler.java:155)
Это фиксится простым удалением из entities.xml битой строки

Далее столнулся со следующей ошибкой:
2024-08-04 19:05:38,157+0300 JiraTaskExecutionThread-12 ERROR admin_localhost 1141x6519x1 2u3go4 0:0:0:0:0:0:0:1 /secure/complete-project-import!resumeImportFromSimulation.jspa ...[c.a.j.imports.xml.DefaultBackupXmlParser] Unexpected import failure java.lang.NullPointerException at com.atlassian.greenhopper.imports.LexorankPreImportHandler.addFieldMapping(LexorankPreImportHandler.java:87) at com.atlassian.greenhopper.imports.LexorankPreImportHandler.handleEntity(LexorankPreImportHandler.java:73) at com.atlassian.jira.imports.project.ao.handler.ChainedAoSaxHandler.endRow(ChainedAoSaxHandler.java:289) at com.atlassian.jira.imports.project.ao.handler.ChainedAoSaxHandler.endElement(ChainedAoSaxHandler.java:193) at com.atlassian.jira.imports.project.ao.handler.ChainedAoSaxHandler.endElement(ChainedAoSaxHandler.java:151) at org.apache.xerces.parsers.AbstractSAXParser.endElement(Unknown Source) at org.apache.xerces.impl.XMLDocumentFragmentScannerImpl.scanEndElement(Unknown Source) at org.apache.xerces.impl.XMLDocumentFragmentScannerImpl$FragmentContentDispatcher.dispatch(Unknown Source) at org.apache.xerces.impl.XMLDocumentFragmentScannerImpl.scanDocument(Unknown Source) at org.apache.xerces.parsers.XML11Configuration.parse(Unknown Source) at org.apache.xerces.parsers.XML11Configuration.parse(Unknown Source) at org.apache.xerces.parsers.XMLParser.parse(Unknown Source)
Решается так:
DELETE FROM "AO_60DB71_LEXORANK";
SELECT id FROM customfield WHERE customfieldtypekey = 'com.pyxis.greenhopper.jira:gh-lexo-rank';
INSERT INTO "AO_60DB71_LEXORANK" ("ID", "FIELD_ID", "ISSUE_ID", "RANK", "TYPE") VALUES (DEFAULT, 10011, -9223372036854775808, '0|000000:', 0);
INSERT INTO "AO_60DB71_LEXORANK" ("ID", "FIELD_ID", "ISSUE_ID", "RANK", "TYPE") VALUES (DEFAULT, 10011, 9223372036854775807, '0|zzzzzz:', 2);
После этого экспортнул ошибка та же, ребутнул та же, реиндекснул та же. Забыл что в начале нужно было делать DELETE FROM "AO_60DB71_LEXORANK"; Решается по этой доке: https://confluence.atlassian.com/jirakb/project-import-of-software-project-fails-with-unexpected-import-failure-803603791.html
Затем нужно снова делать экспорт, и удалять из него строку с entityId '835006' В итоге пофиксилось:
select("""select * from ENTITY_PROPERTY where id = 835006;""")
update("""DELETE from ENTITY_PROPERTY where id = 835006;""")
Попробовал экспортировать сразу 10 проектов и столкнулся со следующей ошибкой:
Some errors occurred:
[Board SCRUM@@ABPLATFORM board@@6358ebcbd66d8108a123b7c7@@Filter for ABPLATFORM board] [Board SCRUM@@ABPLATFORM board@@6358ebcbd66d8108a123b7c7@@Filter for ABPLATFORM board] Trying to export Board found problem: Could not find object: User with key 557058:64d63ec4-f434-47e8-896d-8d4670c131df
[Board SCRUM@@AN board@@rk:821a1326-e179-4fba-bc79-b674adbe8dd9@@Filter for AN board] [Board SCRUM@@AN board@@rk:821a1326-e179-4fba-bc79-b674adbe8dd9@@Filter for AN board] Trying to export Board found problem: Could not find object: Field with id: customfield_10027 used as board statistic
[Board SCRUM@@(to delete) LUTP board@@ug:01cb188b-b27f-464e-9eb0-826197a33f7b@@Filter for LUTPS board] [Board SCRUM@@(to delete) LUTP board@@ug:01cb188b-b27f-464e-9eb0-826197a33f7b@@Filter for LUTPS board] Trying to export Board found problem: Could not find object: User with key 6152d955048360006ae4efa3
[Board KANBAN@@(to delete) 5-11 LUTP kanban@@634d50bed66d8108a1211988@@Filter for 5-11 LUTP kanban] [Board KANBAN@@(to delete) 5-11 LUTP kanban@@634d50bed66d8108a1211988@@Filter for 5-11 LUTP kanban] Trying to export Board found problem: Could not find object: User with key 6152d955048360006ae4efa3
At Most frequent errors found in export you can find advice to fix these problems
Здесь просто запускаем второй раз экспорт без досок, фильтров и дэшбордов.
И следующая ошибка:
Found custom field with values for issues in exported project(s), with id: 10368, name: Checklist Text (view-only) and type: com.atlassian.plugins.atlassian-connect-plugin:com.herocoders.plugins.jira.issuechecklist-free__checklist-text-view-only. This field does not exist. Check if its type is defined in a plugin that has been disabled.
Здесь кажется ругается на чеклист. Можно забэкапить данные в json а потом удалить:
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.ofbiz.OfBizDelegator
import groovy.sql.GroovyRowResult
import groovy.sql.Sql
import org.ofbiz.core.entity.ConnectionFactory
import org.ofbiz.core.entity.DelegatorInterface
import groovy.json.*
import java.sql.Connection
def checklist = select("""SELECT (jp.pkey || '-' || ji.issuenum) AS issuekey, ji.*
FROM project jp
JOIN jiraissue ji ON jp.id = ji.project
WHERE ji.id in (select issue from customfieldvalue where customfield = 10368);""")//.issue
new JsonBuilder( checklist ).toPrettyString()
//select("""select issue from customfieldvalue where customfield = 10013;""")
//update("""DELETE FROM customfieldvalue WHERE customfield = 10013;""")
//update("delete/insert ...")
def select(String query) {
OfBizDelegator delegator = ComponentAccessor.getOfBizDelegator();
DelegatorInterface delegatorInterface = delegator.getDelegatorInterface();
String helperName = delegatorInterface.getGroupHelperName("default");
Connection connection = ConnectionFactory.getConnection(helperName);
Sql sql = new Sql(connection);
List<GroovyRowResult> resultRows = []
try {
resultRows.addAll(sql.rows(query));
} finally {
//sql.close()
connection.close()
}
return resultRows
}
def update(String query) {
OfBizDelegator delegator = ComponentAccessor.getOfBizDelegator();
DelegatorInterface delegatorInterface = delegator.getDelegatorInterface();
String helperName = delegatorInterface.getGroupHelperName("default");
Connection connection = ConnectionFactory.getConnection(helperName);
Sql sql = new Sql(connection);
def numberOfUpdated
try {
numberOfUpdated = sql.executeUpdate(query);
} finally {
//sql.close()
connection.close()
}
return numberOfUpdated
}
А затем нужно изменить учетки пользователей. Следующим скриптом
import com.atlassian.jira.user.ApplicationUser
import com.atlassian.jira.bc.user.ApplicationUserBuilderImpl
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.util.AttachmentUtils
//import com.atlassian.jira.util.PathUtils
import org.apache.log4j.Level
import org.apache.log4j.Logger
//Logger.getLogger("com.onresolve.scriptrunner").setLevel(Level.DEBUG)
def myCSV = 'userRenames.csv' // MUST BE A TEXT FILE
def issue = ComponentAccessor.getIssueManager().getIssueObject("EM-1806") // Key for attachment issue
def aM= ComponentAccessor.getAttachmentManager()
def uM = ComponentAccessor.getUserManager()
def attachments = aM.getAttachments(issue)
def userSearchService = ComponentAccessor.getUserSearchService()
attachments.each{ a ->
def fileName = a.filename
log.warn fileName + ' is the user source'
// This method to get the file path is deprecated, but it works
def filePath = AttachmentUtils.getAttachmentFile(a)
// Concatenation only works for projects which have never edited their project key
// Anyone know a way to get the original project key?
// def pathManager = ComponentAccessor.getAttachmentPathManager()
// def filePath = PathUtils.joinPaths(pathManager.attachmentPath, issue.projectObject.key, '10000', issue.key, a.id.toString())
if (fileName.toString() == myCSV) {
File fileContents = new File(filePath.toString())
def mailChange = 0
def displayNameChange = 0
if (fileContents) {
def lines = fileContents.readLines()
for (item in lines) {
def columns = item.split(",")
def oldUserName = columns[0]
def newMail = columns[2]
def newUserName = columns[3]
def newDisplayName = columns[4]
// log.info "Old mail is " +oldMail + " and new mail is " +newMail // items before change
def user = uM?.getUserByName(oldUserName) as ApplicationUser
def userCheck = uM?.getUserByName(newMail) as ApplicationUser
log.info user.toString()
// Excel compatible logging: item|action|result
if ((! user) && (! userCheck)) {
log.info oldUserName + "|" + "No action|User does not exist" // log when no user exists
}
if ((userCheck) && (! user)) {
log.info oldUserName + "|" + "No action|Only new account " + newMail + " exists; nothing to do" // log when only new account exits
}
if ((userCheck) && (user)) {
log.info oldUserName + "|" + "Duplicate|Migration required for " + newMail // log when two accounts are present
}
if ((user) && (! userCheck)) {
// def check = uM.canRenameUser(user) // Boolean in case perms or directory type is in question
//log.info "Check was " +check
def nMail = new ApplicationUserBuilderImpl(user).emailAddress(newMail.toLowerCase()).build()
uM.updateUser(nMail)
mailChange = 1
log.info oldUserName + "|" + "Action|Email was uptated to " + newMail // log when user is updated
}
}
}
if (mailChange == 1) {
// Yes, you have to re-declare the user to perform a second update
def lines = fileContents.readLines()
for (item in lines) {
def columns = item.split(",")
def oldUserName = columns[0]
def oldMail = columns[1]
def newMail = columns[2]
def newUserName = columns[3]
def newDisplayName = columns[4]
def user = uM?.getUserByName(oldUserName) as ApplicationUser
if (user) {
def nDisplayName = new ApplicationUserBuilderImpl(user).displayName(newDisplayName).build()
uM.updateUser(nDisplayName)
log.info oldMail + "|Action|Display name was updated to " + nDisplayName
displayNameChange = 1
}
}
}
if (displayNameChange == 1) {
def lines = fileContents.readLines()
for (item in lines) {
def columns = item.split(",")
def oldUserName = columns[0]
def oldMail = columns[1]
def newMail = columns[2]
def newUserName = columns[3]
def newDisplayName = columns[4]
def user = uM?.getUserByName(oldUserName) as ApplicationUser
def userCheck = uM?.getUserByName(newUserName) as ApplicationUser
if ((user) && (! userCheck)) {
def nName = new ApplicationUserBuilderImpl(user).name(newUserName.toLowerCase()).build()
uM.updateUser(nName)
log.info oldUserName + "|Action|User name was updated to " + nName
}
}
}
}
}
Так же потребовалось удалить все фильтры
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.search.SearchRequest
import com.atlassian.jira.issue.search.SearchRequestManager
import com.atlassian.jira.user.ApplicationUser
def userUtil = ComponentAccessor.getUserUtil()
SearchRequestManager searchRequestManager = ComponentAccessor.getComponent( SearchRequestManager )
def users = ComponentAccessor.userManager.getAllUsers()
def filters = users.collect{
searchRequestManager.getAllOwnedSearchRequests(it)
}
filters.collect{it.id.collect{searchRequestManager.delete(it)}}
Следующая ошибка Data import for project with key: NLTCS Data import failed with exception: Entity com.atlassian.greenhopper.service.sprint.SprintAO does not have field CREATED_DATE java.lang.NullPointerException: Entity com.atlassian.greenhopper.service.sprint.SprintAO does not have field CREATED_DATE at java.base/java.util.Objects.requireNonNull(Unknown Source) at net.java.ao.EntityManager.create(EntityManager.java:342) at net.java.ao.EntityManager.create(EntityManager.java:402)
Решается так:
ALTER TABLE "AO_60DB71_SPRINT"
DROP COLUMN "CREATED_DATE";
Еще одна ошибка: Data import failed with exception: Entity com.atlassian.greenhopper.service.sprint.SprintAO does not have field SPRINT_VERSION java.lang.NullPointerException: Entity com.atlassian.greenhopper.service.sprint.SprintAO does not have field SPRINT_VERSION at java.base/java.util.Objects.requireNonNull(Unknown Source) at net.java.ao.EntityManager.create(EntityManager.java:342)
update("""ALTER TABLE "AO_60DB71_SPRINT"
DROP COLUMN "SPRINT_VERSION";""")
Data import failed with exception: null java.lang.NullPointerException at com.awnaba.projectconfigurator.transporter.engine.JiraDataImporter.getProcessedCustomFieldMessageSet(JiraDataImporter.java:336) at com.awnaba.projectconfigurator.transporter.engine.JiraDataImporter.addMessagesFromMappingResult(JiraDataImporter.java:306) at com.awnaba.projectconfigurator.transporter.engine.JiraDataImporter.jiraImportData(JiraDataImporter.java:253) at com.awnaba.projectconfigurator.transporter.engine.JiraDataImporter.importData(JiraDataImporter.java:153) at com.awnaba.projectconfigurator.transporter.engine.JiraDataImporter$DataImportCallable.call(JiraDataImporter.java:410) at com.awnaba.projectconfigurator.transporter.engine.JiraDataImporter.importDataSynchronously(JiraDataImporter.java:380) at com.awnaba.projectconfigurator.operationsapi.impl.ProjectCompleteImporterImpl.doDataLoadPhase(ProjectCompleteImporterImpl.java:427) at com.awnaba.projectconfigurator.operationsapi.impl.ProjectCompleteImporterImpl.executeNextPhase(ProjectCompleteImporterImpl.java:316) at com.awnaba.projectconfigurator.operationsapi.impl.ProjectCompleteImporterImpl.doCompleteImport(ProjectCompleteImporterImpl.java:281) at com.awnaba.projectconfigurator.operationsapi.impl.ProjectCompleteImporterImpl.access$0(ProjectCompleteImporterImpl.java:274) at com.awnaba.projectconfigurator.operationsapi.impl.ProjectCompleteImporterImpl$CompleteImporterTask.call(ProjectCompleteImporterImpl.java:546) at com.awnaba.projectconfigurator.operationsapi.impl.ProjectCompleteImporterImpl$CompleteImporterTask.call(ProjectCompleteImporterImpl.java:1) at com.awnaba.projectconfigurator.longrunningtasks.TaskHelperImpl$WrappingTask.safeCall(TaskHelperImpl.java:183) at com.awnaba.projectconfigurator.longrunningtasks.TaskHelperImpl$WrappingTask.call(TaskHelperImpl.java:132) at com.awnaba.projectconfigurator.longrunningtasks.TaskHelperImpl$WrappingTask.call(TaskHelperImpl.java:1) at com.atlassian.jira.task.TaskManagerImpl$TaskCallableDecorator.call(TaskManagerImpl.java:533) at com.atlassian.jira.task.TaskManagerImpl$TaskCallableDecorator.call(TaskManagerImpl.java:491) at java.base/java.util.concurrent.FutureTask.run(Unknown Source) at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source) at java.base/java.util.concurrent.FutureTask.run(Unknown Source) at com.atlassian.jira.task.ForkedThreadExecutor$ForkedRunnableDecorator.run(ForkedThreadExecutor.java:216) at java.base/java.lang.Thread.run(Unknown Source) https://appfire.atlassian.net/wiki/spaces/SUPPORT/pages/80884866/PCJ-Troubleshooting+a+NullPointerException+caused+due+to+a+null+text+value+for+a+custom+field+value Решается так:
delete from customfieldvalue where id in(Select id from customfieldvalue where STRINGVALUE is null and NUMBERVALUE is null and TEXTVALUE is null and DATEVALUE is null and VALUETYPE is null);
Новая ошибка: ERRORS: The Issue Link Type 'Issue split' has style 'gh_split_issue' in the backup, but has no style in the current system. Решается так:
UPDATE issuelinktype
SET pstyle = null
WHERE id = 10212;