Updating JIRA links in Confluence when migrating to new server

We just finished the process of migrating our internal JIRA server running on Windows to a new Ubuntu machine. The migration process went fairly smooth. However, the URL of JIRA has also changed and we have a lot of links in our Confluence wiki pointing to JIRA.

To update all the links manually, would be tedious and error-prone, so I used some Groovy together with the excellent Confluence Command Line Interface to make this automatic.

This is the main loop of my program, no surprises there:

    public static void main(String[] args)
    {
        logger.info("Getting configuration");
        ConfigurationLoader loader = new ConfigurationLoader();
        Configuration configuration = loader.getConfiguration();

        List<String> spaces = getAllSpaces(configuration);
        for (String spaceName: spaces)
        {
            logger.debug("Updating space {}", spaceName)

            List<String> pageNames = getAllPages(configuration, spaceName);
            for (String pageName: pageNames)
            {
                logger.debug("  Updating page {}", pageName)
                updateJiraUrl(configuration, spaceName, pageName);
            }
        }
    }

The ConfigurationLoader and the Configuration objects are just keeping track of what the URL of confluence is, and the path to the Confluence CLI. So, the actual code first gets all the spaces, then all the pages into that space and then we try to update any old JIRA URL we find on that page.

The function to get all the spaces call the Confluence CLI and then does some string magic on the result to just get the keys of the spaces only:

    static List<String> getAllSpaces(LiquidPlannerConfiguration configuration)
    {
        File tempFile = File.createTempFile("confluence-spaces-list", "txt")
        ProcessBuilder processBuilder = new ProcessBuilder();
        processBuilder.command(configuration.pathToConfluenceCli,
                               "--server",
                               configuration.confluenceInstallationUrl,
                               "--user",
                               configuration.confluenceUser,
                               "--password",
                               configuration.confluencePassword,
                               "--action",
                               "getSpaceList",
                               "--file",
                               tempFile.getAbsolutePath())

        processBuilder.redirectErrorStream(true)
        def Process process = processBuilder.start()
        writeProcessOutput(process);
        int processResult = process.waitFor()
        logger.info("Got result from process: {}", processResult)

        List<String> result = new ArrayList<String>();
        tempFile.eachLine {
            def spaceKey = it.find(/^"[A-Z0-9]*"/);
            if (spaceKey)
            {
                result.add(spaceKey.substring(1, spaceKey.length() - 1))
            }
        }

        tempFile.delete();
        return result;
    }

For each space key, we get a list of all the page names:

    static List<String> getAllPages(LiquidPlannerConfiguration configuration, String spaceKey)
    {
        File tempFile = File.createTempFile("confluence-pages-list", "txt")
        ProcessBuilder processBuilder = new ProcessBuilder();
        processBuilder.command(configuration.pathToConfluenceCli,
                               "--server",
                               configuration.confluenceInstallationUrl,
                               "--user",
                               configuration.confluenceUser,
                               "--password",
                               configuration.confluencePassword,
                               "--action",
                               "getPageList",
                               "--space",
                               spaceKey,
                               "--file",
                               tempFile.getAbsolutePath())

        processBuilder.redirectErrorStream(true)
        def Process process = processBuilder.start()
        writeProcessOutput(process);
        int processResult = process.waitFor()
        logger.info("Got result from process: {}", processResult)

        List<String> result = new ArrayList<String>();
        tempFile.eachLine {
            result.add(it);
        }

        tempFile.delete();
        return result;
    }

For update of the page, we first need to get the content of the page and then update the page:

    static void updateJiraUrl(LiquidPlannerConfiguration configuration, String spaceName, String pageName)
    {
        File pageContents = null;
        try
        {
            pageContents = getPageContent(configuration, spaceName, pageName);
            updatePageContents(configuration, spaceName, pageName, pageContents);
        }
        finally
        {
            if( pageContents )
            {
                pageContents.delete()
            }
        }

    }

Getting the content of the page is just calling Confluence CLI:

    static File getPageContent(LiquidPlannerConfiguration configuration, String spaceName, String pageName)
    {
        File tempFile = File.createTempFile(spaceName + "-page", "txt")
        ProcessBuilder processBuilder = new ProcessBuilder();
        processBuilder.command(configuration.pathToConfluenceCli,
                               "--server",
                               configuration.confluenceInstallationUrl,
                               "--user",
                               configuration.confluenceUser,
                               "--password",
                               configuration.confluencePassword,
                               "--action",
                               "getSource",
                               "--space",
                               spaceName,
                               "--title",
                               pageName,
                               "--file",
                               tempFile.absolutePath)

        processBuilder.redirectErrorStream(true)
        def Process process = processBuilder.start()
        writeProcessOutput(process);
        int processResult = process.waitFor()
        logger.info("Got result from process: {}", processResult)

        return tempFile;
    }

Updating the page is done here:

    static void updatePageContents(LiquidPlannerConfiguration configuration, String spaceName, String pageName, File pageContents)
    {
        if( !pageContents.text.contains( "companyweb.company.com:8888/jira") )
        {
            return;
        }

        File replacedFile = File.createTempFile(spaceName + "-page-replaced", "txt");

        def replacedFileWriter = new FileWriter( replacedFile );
        new FileReader( pageContents ).transformLine( replacedFileWriter ) {
            it.replaceAll("companyweb\\.company\\.com:8888/jira", "jira\\.company\\.com:8888")
        }
        
        ProcessBuilder processBuilder = new ProcessBuilder();
        processBuilder.command(configuration.pathToConfluenceCli,
                               "--server",
                               configuration.confluenceInstallationUrl,
                               "--user",
                               configuration.confluenceUser,
                               "--password",
                               configuration.confluencePassword,
                               "--action",
                               "storePage",
                               "--space",
                               spaceName,
                               "--title",
                               pageName,
                               "--file",
                               replacedFile.absolutePath)

        processBuilder.redirectErrorStream(true)
        def Process process = processBuilder.start()
        writeProcessOutput(process);
        int processResult = process.waitFor()
        logger.info("Got result from process: {}", processResult)
    }

There are 2 pieces of code in this last function that I would like to highlight:

if( !pageContents.text.contains( "companyweb.company.com:8888/jira") )
{
   return;
}

This part just reads the text of the confluence page which we saved to a file and checks if the old URL is present. If it is not present, we just return from the method and thus do not change anything.
Notice how easy it is in Groovy to get the content of a file as a String. The File#getText() method is something that is part of the Groovy JDK. See http://groovy.codehaus.org/groovy-jdk/java/io/File.html for more interesting methods on File added by Groovy.

The 2nd piece of code does the actual replacement, again with a very nice piece of Groovy code:

File replacedFile = File.createTempFile(spaceName + "-page-replaced", "txt");

def replacedFileWriter = new FileWriter( replacedFile );
new FileReader( pageContents ).transformLine( replacedFileWriter ) {
    it.replaceAll("companyweb\\.company\\.com:8888/jira", "jira\\.company\\.com:8888")
}

What we have here is reading from the pageContents file and writing it out to the replacedFile. Just before the write of each line, the closure is called so we can do some transformation on that line. Here, we use the replaceAll method that takes a regular expression to do the URL matching and replacing. Since a dot (.) is a special character, we have to escape it with a backslash (\) and since a backslash is also a special character, we also have to escape that one.

That is all there is to it. I used Confluence CLI 2.4.0 which is the last one at the time of writing and Confluence 3.4 which is the version we have currently at our company.

PS: If you want to run this yourself, you just need 1 more function that reads the output of the Confluence CLI process:

    static void writeProcessOutput(Process process) throws Exception
    {
        InputStreamReader tempReader = new InputStreamReader(
                new BufferedInputStream(process.getInputStream()));
        BufferedReader reader = new BufferedReader(tempReader);
        while (true)
        {
            String line = reader.readLine();
            if (line == null)
                break;
            System.out.println(line);
        }
    }
Advertisements

2 thoughts on “Updating JIRA links in Confluence when migrating to new server

  1. It’s nice doing little automation things like this with groovy, and helps to keep your hand in for people that don’t get to do much coding, like me. Interesting post. A couple of suggestions though – when we had the same issue I just created apache rewrite rules for this… which if you’re already front Confluence with Apache is easy enough. Apache can listen on multiple ports and you can get a DNS entry for the old alias pointing to the new. Then this will also work for links embedded in old mails, Word docs, etc etc.

    My other suggestion would be to just link in the confluence CLI and call the method directly, to avoid the overhead of java process startup. If you have thousands of pages with these links, that could be a non-trivial difference.

  2. We do not front Confluence with apache, so that was not possible. Thanks for pointing that out though.

    I don’t really get what you mean with ‘link in the confluence CLI and call the method directly’ ?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s