A Simple Guide to Creating a Bitbucket Plugin

Alaeddine Selmi
willhaben Tech Blog
9 min readOct 21, 2020

--

Introduction

Bitbucket offers the possibility of implementing repository hooks to add functionalities or enforce policies in your version control system. These hooks can be either local or on the server side. Clearly, a local hook only affects your local copy of the repository, whereas a server side hook works for all repository descendants, so long as it is added to the origin repository. The second option is obviously the better idea if you want to enforce policies among all collaborators.

However, according to Bitbucket’s documentation, creating a server side hook is strongly discouraged. You can read more about this here. As an alternative, they suggest using the Bitbucket Java Plugin Development Framework to create a hook wrapped inside a Bitbucket plugin.

The next sections will guide you through implementing, testing, and setting up a simple Bitbucket plugin that implements a pre-repository hook. More info about different Bitbucket repository hook types could be found here.

Install Atlassian SDK

The first step is to install the Atlassian SDK, so you can add useful commands for creating and testing plugins. The following guides can help you to set it up on your machine:

Prerequisites

Depending on your Bitbucket server version and the JRE running on it, there are important prerequisites to consider:

  • Before Bitbucket server 6.0: Although installing the SDK with a higher Java version would work, it is better to keep using Java 8 while working on the plugin. In fact, compiling with a different Java version could lead to inconsistencies at runtime.
  • Starting from Bitbucket server 6.0: As mentioned in the release notes, Bitbucket 6.0 added support for Java 11, but it also still supports Java 8. This means it is possible to use both Java versions. Ideally, you should use the same distribution and version as the one on top of which your Bitbucket server is installed. Otherwise you could face at runtime problems saying that the plugin was compiled with a higher Java version.
  • You need to have Maven installed in order to be able to build the Bitbucket plugin module.

Create and Initialize the Plugin Skeleton

After the SDK is installed, it is time to create and initialize the Maven plugin project. The first step is to create the project skeleton using the following command:

Atlas-create-bitbucket-plugin

Next, the command line interface will prompt you to set the following values:

  • Group Id: e.g., at.willhaben.bitbucket.plugin
  • Artifact Id: e.g., commit-message-vaidator-plugin
  • Use OSGi Java Config: Y

For the rest, just pressing “Enter” is sufficient, as this is a basic plugin.

The project generated by the above mentioned command contains the sample component classes MyPluginComponent and MyPluginComponentImpl, test classes, resources files (css, images, js, xml) and a Pom file. The important files and resources to keep are the pom.xml file and the atlassian-plugin.xml which is the descriptor where the plugin’s class implementation is declared and looks something like this:

<atlassian-plugin key=”${atlassian.plugin.key}” name=”${project.name}” plugins-version=”2">
<plugin-info>
<description>${project.description}</description>
<version>${project.version}</version>
<vendor name=”${project.organization.name}” url=”${project.organization.url}” />
<param name=”plugin-icon”>images/pluginIcon.png</param>
<param name=”plugin-logo”>images/pluginLogo.png</param>
</plugin-info>
</atlassian-plugin>

Adjusting the Pom File

Most likely, the project will not compile from the first shot. You will need to add a Maven central repository and the Bitbucket repositories, so that the dependencies (libraries, plugins) are downloaded:

<pluginRepositories>
<pluginRepository>
<id>spring-libs-milestones-plugins-repository</id>
<name>Spring lib M Repository</name>
<url>https://repo.spring.io/libs-milestone/</url>
</pluginRepository>
<pluginRepository>
<id>maven-central-plugin-repository</id>
<name>Maven Central Plugin Repository</name
<url>https://repo.maven.apache.org/maven2</url>
</pluginRepository>
</pluginRepositories>
<repositories>
<repository>
<id>spring-libs-milestones-repository</id>
<name>Spring lib M Repository</name>
<url>https://repo.spring.io/libs-milestone/</url>
</repository>
<repository>
<id>maven-central-repository</id>
<name>Maven Central Repository</name>
<url>https://repo.maven.apache.org/maven2</url>
</repository>
</repositories>

The bitbucket-maven-plugin plugin module is responsible for the compilation and bundling of the project into an installable file (supported formats are Jar and Obr — an OSGi bundle repository supported by Bitbucket servers). By default, it is configured to use Java 8, so make sure not to include dependencies that need a higher version, as this would lead to inconsistencies during the build.

Using a different Java Version

The plugin created by the above mentioned SDK command is destined for Bitbucket 5.16.0 which basically supports Java 8. Eventually, you will have a newer Bitbucket server running on a higher Java version (e.g., Java 11). That means you will need to do some Pom clean up and version upgrades before continuing. I have a Bitbucket Server 7.5 running on top of Java 11, so I had to go through the following steps:

1- I changed the Bitbucket server version in the properties to the desired version, 7.5.0 (note the use of semantic versioning):

<bitbucket.version>7.5.0</bitbucket.version>

2. The Maven build command would automatically update the project dependencies (e.g., bitbucket-api) and freshly download them from the springs-libs-milestones-repository. The first build failed due to a missing dependency, so I just added them in order to finish building.

<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>

3. I updated the Java version used by the maven-compiler-plugin to 11.

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>

4. I told the maven-compiler-plugin to use Java 11 as well.

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>

Implementation

After creating a Maven compilable project with a clean Pom, it is time for the concrete implementation. There are different interfaces to be implemented, depending on what you want to achieve. As mentioned in the beginning, this article focuses on creating a pre-repository hook.

As a first step, create a plugin module using the following SDK command:

Atlas-create-bitbucket-plugin-module

The command line will prompt you to choose the module type. Currently, the following types are available: (Commit Indexer, Keyboard Shortcut, Repository Hook, SCM Request Check, SSH Request Handler, Component Import, Component, Downloadable Plugin Resource, Licensing API Support, Module Type, REST Plugin Module, Servlet Context Listener, Servlet Context Parameter, Servlet Filter, Servlet, Template Context Item, Web Item, Web Panel Renderer, Web Resource, Web Resource Transformer).

You should choose option 3 to create a repository hook plugin. When selecting the type, give the hook’s class/package name and answer the rest of the prompt messages with “N,” if there are no special requirements and/or additional modules. Then, the generated class is a Java class that implements the PreReceiveRepositoryHook interface. Its onReceive() method serves to implement some business logic whenever it receives an action on that repository (e.g., pushing a set of commits).

In the current example, I wanted to create some sort of control on the commit message whenever it is pushed to the origin. To achieve that, I decided to implement the PreRepositoryHook instead, as its preUpdate() method takes an PreRepositoryHookContext object in parameter which allows me to register a PreRepositoryHookCommitCallback object. The callback interface has a set of methods to be implemented depending on the action to be performed vis-à-vis a commit action. I therefore implemented the onCommitAdded() method as a way of enforcing some checks on the commit message before pushing in to the origin repository. The new implemented class representing the hook is automatically added to the descriptor file atlassian-plugin.xml. Any changes to the class name and/or its containing package should be updated in that file.

Installation

As mentioned above, the maven-bitbucket-plugin generates a file bundle installable like the one on the Bitbucket server. This is achieved by uploading it in the Admin section under Add-ons > Manage Apps, from the left side menu.

uploading a plugin file

When I uploaded the plugin for the first time, Bitbucket failed to enable it, due to a BundleException exception caused by missing requirements for the OSGi bundle. It simply means that something went wrong during the bundling of the maven-bitbucket-plugin. I fixed this by changing the default configuration of the plugin’s execution rules.

This is how the configuration looked like after I resolved all the BundleException problems:


<plugin>
<groupId>com.atlassian.maven.plugins</groupId>
<artifactId>bitbucket-maven-plugin</artifactId>
<version>${amps.version}</version>
<extensions>true</extensions>
<configuration>
<products>
<product>
<id>bitbucket</id>
<instanceId>bitbucket</instanceId>
<version>${bitbucket.version}</version>
<dataVersion>${bitbucket.data.version}</dataVersion>
</product>
</products>
<instructions>
<_noee>true</_noee>
<Atlassian-Plugin-Key>${atlassian.plugin.key}</Atlassian-Plugin-Key>

<! — Add package to export here →
<Export-Package>at.willhaben.bitbucket.api,</Export-Package> <! — Add package import here → <Import-Package>
org.springframework.osgi.*;version=”0";resolution:=”optional”,
org.eclipse.gemini.blueprint.*;version=”0";resolution:=”optional”,
*;version=”0";resolution:=optional
</Import-Package>
<! — Ensure plugin is Spring powered →

<Spring-Context>*</Spring-Context>
</instructions>
</configuration>
</plugin>

Note the explicit declaration of the packages to import in the <instructions> section of the plugin’s configuration.

Testing Using altas-run and atlas-debug

The fastest and most straightforward way to test the plugin is to use the built-in SDK commands, which start a Bitbucket server with the plugin already pre-installed. Those commands are:

  • atlas-run → This downloads and runs a lightweight Bitbucket server under the /target/bitbucket directory. The server version is the one mentioned in the Pom properties.
  • atlas-debug → This does the same thing as the previous command, but while running the server in debug mode, making it possible to attach a remote debugger through your IDE (see the next section).

The log files can be found under the /target/bitbucket/home/log/atlassian-bitbucket.log directory. It is important to check the logs and check for problems that may occur, particularly while the server enables the plugin using the plugin enabler. Once the Bitbucket server has started, the standard output will log the server’s URL. In my case, the URL is http://localhost:7990/bitbucket (default credentials: admin/admin). It comes with a sample project and a repository attached to it. The plugin is pre-installed; this is how it appears in the list of add-ons:

Bitbucket manage add-ons page

Enabling the repository hook is therefore done under the hook section of the Repository Settings (Admin):

Enable repository hook

Attach a Remote Debugging Instance in Intellij

This will help you debug your code to see how it behaves when you perform the wanted actions, what problems have occurred, etc. As you run the server using the atlas-debug command, you will need to attach a remote debugging instance to it on Intellij. A remote debugger can be created under the menu Run > Edit Configurations in Intellij. Then, add a new remote debugger listening to port 5005 (see the example below).

Remote debugging

Running it in debug mode, of course, would be sufficient for attaching a debugging instance to the server. Obviously, you will need to set the correct module classpath corresponding to the plugin.

Installing Plugin in a Dockerized Bitbucket Server

Thanks to docker, downloading and installing a local Bitbucket server can be done with a couple of simple steps. Once docker is installed, you just need to pull the Atlassian Bitbucket Container Image from the Docker hub and start it using the following commands:

$> docker pull atlassian/bitbucket-server:<version>
$> docker volume create — name bitbucketVolume
$> docker run -v bitbucketVolume:/var/atlassian/application-data/bitbucket — name=”bitbucket” -d -p 7990:7990 -p 7999:7999 atlassian/bitbucket-server

If everything goes well and the docker container started properly, you should be able to access the server at the localhost (localhost:7990/bitbucket).

At the beginning, you will need to create a Bitbucket trial license using an email address, so you can set up the Bitbucket application. Once that is done, you can upload your jar to the add-on section, as previously mentioned, and enable the hook for a repository of your choice.

If the licence is expired, you need to remove the container and it’s corresponding docker volume and newly create everything again.

Conclusion

There a couple of things to consider when implementing a bitbucket plugin. The most important ones could be briefly summarized as follows:

  • The default Pom file is outdated, so make sure to clean it and update the versions of the dependencies.
  • Make sure the configured Bitbucket version is similar to the target Bitbucket server instance.
  • To develop and compile the plugin, use the same Java version as the one on top of which the target Bitbucket server is installed.
  • You will need to configure the maven-bitbucket-plugin with some tweaks so that the Bitbucket server can enable the plugin bundle.

--

--