Creating Cross-Product Add-ons for Atlassian ecosystem

Some add-ons introduce generic functionality that may be useful across Atlassian Server products.  Rather than maintaining duplicate code for each product, you can create a genric add-on that runs across products.  And fortunately its relatively simple to accomplish.

The article below outlines the necessary configuration to write an add-on that will run in products of your choice.

compatible.PNG

Common vs. Product Specific APIs & Services

The first, and perhaps obvious thing to note is that your code will need to rely on the apis and services available across all the products you plan to support.  This gets complicated in some areas like User Management due to the staggered pace of upgrades across products.  We'll also talk about work-arounds when you just need a product specific API.

Common Dependencies in pom.xml

You'll want to declare the base platform APIs in your pom.xml.  This is libraries/services common to all products, and gives you most basic functionality (admin pages, servlets, REST, etc).

<dependencies>
        <dependency>
            <groupId>com.atlassian.platform</groupId>
            <artifactId>platform</artifactId>
            <version>${platform.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
...
</dependencies>

At the time of this writing 3.2.0 was a recent compatible version, but you can always search https://maven.atlassian.com for latest.

 

And of course you can't use the 'jira-maven-plugin' or any product specific AMPS plugin, but instead the generic one.

 <build>
        <plugins>
            <plugin>
                <groupId>com.atlassian.maven.plugins</groupId>
                <artifactId>maven-amps-plugin</artifactId>
                <version>${amps.version}</version>
                <extensions>true</extensions>
                ...
            </plugin>
       ...
       </plugins>
        ....
</build>

** Starting the product now with atlas-run will default to RefApp, we'll fix that below.

 

Running add-on within specific products

The atlas-run command supports a few flags that indicate which product to use, namely --product and --version.

atlas-run --product bamboo --version 5.13.1

Now you can fire up your add-on in each product individually and confirm it works.

Using Product Specific Components

The snippet above is enough to let you write a basic add-on with a servlet or REST api, but you may need access to product specific services. And to do that you'll need to add dependencies for each product.

<dependency>
            <groupId>com.atlassian.jira</groupId>
            <artifactId>jira-api</artifactId>
            <version>${jira.version}</version>
            <scope>provided</scope>
            <exclusions>
                <exclusion>
                    <groupId>javax.jms</groupId>
                    <artifactId>jms</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>jta</groupId>
                    <artifactId>jta</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>xml-apis</groupId>
                    <artifactId>xml-apis</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>com.atlassian.confluence</groupId>
            <artifactId>confluence-java-api</artifactId>
            <version>${confluence.version}</version>
            <scope>provided</scope>
            <exclusions>
                <exclusion>
                    <groupId>javax.jms</groupId>
                    <artifactId>jms</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>jta</groupId>
                    <artifactId>jta</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>com.atlassian.bamboo</groupId>
            <artifactId>atlassian-bamboo-api</artifactId>
            <version>${bamboo.version}</version>
            <scope>provided</scope>
            <exclusions>
                <exclusion>
                    <groupId>javax.jms</groupId>
                    <artifactId>jms</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>jta</groupId>
                    <artifactId>jta</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>xml-apis</groupId>
                    <artifactId>xml-apis</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.apache.httpcomponents</groupId>
                    <artifactId>httpclient</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>com.atlassian.stash</groupId>
            <artifactId>stash-api</artifactId>
            <version>${stash.version}</version>
            <scope>provided</scope>
            <exclusions>
                <exclusion>
                    <groupId>javax.jms</groupId>
                    <artifactId>jms</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>jta</groupId>
                    <artifactId>jta</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>com.atlassian.bitbucket.server</groupId>
            <artifactId>bitbucket-api</artifactId>
            <version>${bitbucket.version}</version>
            <scope>provided</scope>
            <exclusions>
                <exclusion>
                    <groupId>javax.jms</groupId>
                    <artifactId>jms</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>jta</groupId>
                    <artifactId>jta</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>com.atlassian.fisheye</groupId>
            <artifactId>atlassian-fisheye-api</artifactId>
            <version>${fecru.version}</version>
            <scope>provided</scope>
            <exclusions>
                <exclusion>
                    <groupId>javax.jms</groupId>
                    <artifactId>jms</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>jta</groupId>
                    <artifactId>jta</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

 

Using AMPs plugin's 'Product' definition for product details

Rather than specifying a version each time and dealing with the different ports and contexts, I chose to make everything run at localhost:5990/product  this simplifies testing and is easier to remember than 6 product specific combos. Just specify the default versions and info as properties:

<properties>
        ....
        <amps.version>6.2.11</amps.version>
        <sal.version>3.1.0</sal.version>
        <platform.version>3.2.0</platform.version>
        <product.httpPort>5990</product.httpPort>
        <product.contextPath>/product</product.contextPath>
        <refapp.version>3.2.2</refapp.version>
        <confluence.version>6.0.4</confluence.version>
        <jira.version>7.3.0</jira.version>
        <jira.servicedesk.version>3.3.2</jira.servicedesk.version>
        <bamboo.version>5.13.1</bamboo.version>
        <bitbucket.version>4.0.0</bitbucket.version>
        <stash.version>3.0.0</stash.version>
        <fecru.version>4.3.0-20170119092650</fecru.version>
    </properties>

 

And extend the amps-maven-plugin with a products block:

    <build>
        <plugins>
            <plugin>
                <groupId>com.atlassian.maven.plugins</groupId>
                <artifactId>maven-amps-plugin</artifactId>
                <version>${amps.version}</version>
                <extensions>true</extensions>
                <configuration>
                    <!--You can also pass these to atlas-run  (\-\- is just xml escaping, use 2 dashes) -->
                    <!-- atlas-run \-\-testGroup bamboo \-\-jvmargs "-Dhttp.proxyHost=www-proxy.lmig.com -Dhttp.proxyPort=80" -->
                    <!--<jvmArgs>-Dhttp.proxyHost=proxyhost.domain -Dhttp.proxyPort=80</jvmArgs>-->
                    <log4jProperties>src/test/resources/log4j.properties</log4jProperties>
                    <enableQuickReload>true</enableQuickReload>
                    <enableDevToolbox>false</enableDevToolbox>
                    <enableFastdev>false</enableFastdev>
                    <compressJs>false</compressJs>
                    <!-- See here for an explanation of default instructions: -->
                    <!-- https://developer.atlassian.com/docs/advanced-topics/configuration-of-instructions-in-atlassian-plugins -->
                    <instructions>
                        <Atlassian-Plugin-Key>${atlassian.plugin.key}</Atlassian-Plugin-Key>

                        <!-- Add package to export here -->


                        <!-- Add package import here -->


                        <!-- Ensure plugin is spring powered -->
                        <Spring-Context>*</Spring-Context>
                    </instructions>
                    <products>
                        <product>
                            <id>refapp</id>
                            <version>${refapp.version}</version>
                            <httpPort>${product.httpPort}</httpPort>
                            <contextPath>${product.contextPath}</contextPath>
                        </product>
                        <product>
                            <id>jira</id>
                            <version>${jira.version}</version>
                            <httpPort>${product.httpPort}</httpPort>
                            <contextPath>${product.contextPath}</contextPath>
                        </product>
                        <product>
                            <id>jira</id>
                            <instanceId>jira-sd</instanceId>
                            <version>${jira.version}</version>
                            <httpPort>${product.httpPort}</httpPort>
                            <contextPath>${product.contextPath}</contextPath>
                            <applications>
                                <application>
                                    <applicationKey>jira-servicedesk</applicationKey>
                                    <version>${jira.servicedesk.version}</version>
                                </application>
                            </applications>
                        </product>
                        <product>
                            <id>confluence</id>
                            <version>${confluence.version}</version>
                            <httpPort>${product.httpPort}</httpPort>
                            <contextPath>${product.contextPath}</contextPath>
                            <productDataVersion>${confluence.version}</productDataVersion>
                        </product>
                        <product>
                            <id>bamboo</id>
                            <version>${bamboo.version}</version>
                            <server>localhost</server>
                            <httpPort>${product.httpPort}</httpPort>
                            <contextPath>${product.contextPath}</contextPath>
                        </product>
                        <product>
                            <id>stash</id>
                            <version>${stash.version}</version>
                            <httpPort>${product.httpPort}</httpPort>
                            <contextPath>${product.contextPath}</contextPath>
                        </product>
                        <product>
                            <id>bitbucket</id>
                            <version>${bitbucket.version}</version>
                            <httpPort>${product.httpPort}</httpPort>
                            <contextPath>${product.contextPath}</contextPath>
                        </product>
                        <product>
                            <id>fecru</id>
                            <version>${fecru.version}</version>
                            <httpPort>${product.httpPort}</httpPort>
                            <contextPath>${product.contextPath}</contextPath>
                        </product>
                    </products>

Now you can still use --product when you call atlas-run, but the other details will be set based on common config.

 

 

Specifying Compatibility in atlassian-plugin-marketing.xml

The file atlassian-plugin-marketing.xml is a hndy file ingested by marketplace to automatically configure your marketing page and compatibility info.  I love keeping the source of truth for all things in version control, so we state our cross-product compatibility there.

<atlassian-plugin-marketing>
    <compatibility>
        <product name="jira" min="6.3.1"  max="7.3.1" />
        <product name="bamboo" min="5.10.0" max="5.15.0"/>
        <product name="bitbucket" min="4.0.0" max="4.13.0"/>
        <product name="confluence" min="5.10.2" max="6.0.5"/>
        <!-- fecru uses build-date appended to version in maven repo, so we cant use real versions :( -->
        <product name="fecru" min="4.1.0-20160624111830" max="4.3.0-20170119092650"/>
    </compatibility>
....
</atlassian-plugin-marketing>

Does this really work?

Everything cited in this article can be viewed in the source code of my StatusPage.io integration add-on which supports JIRA (JSD,Software, Core), Bamboo, Bitbucket, Confluence and Fisheye/Crucible.
https://bitbucket.org/eddiewebb/statuspage-banner-atlassian-server/src 

 

How do you test across products?

Testing across all these products and versions manually would be a royal PITA, so please read my upcomming article that dives into the automation of testing across products based on the specs in atlassian-plugin-marketing.xml.
testing.PNG

(and for bonus points we  drive an automated release into Marketplace using Bitbuckets Pipelines !)

 

 

 

0 comments

Comment

Log in or Sign up to comment
Community showcase
Posted Thursday in Marketplace Apps

You + one app + a desert island...

Hi all! My name is Miles and I work on the Marketplace team. We’re looking for better ways to recommend and suggest apps that are truly crowd favorites, so of course we wanted to poll the Community. ...

123 views 3 4
Join discussion

Atlassian User Groups

Connect with like-minded Atlassian users at free events near you!

Find a group

Connect with like-minded Atlassian users at free events near you!

Find my local user group

Unfortunately there are no AUG chapters near you at the moment.

Start an AUG

You're one step closer to meeting fellow Atlassian users at your local meet up. Learn more about AUGs

Groups near you