TeamCity Plugin Development Help

Storing Plugin State and Settings

Most of the plugins need a location to store their state and settings.

By state we mean the internal plugin data which cannot be easily computed from scratch and is usually not visible to the users of the plugin.
The state can be either global or associated with some TeamCity entity, such as a build or a build configuration.

By settings we understand user-defined settings, specified either via the user interface or DSL.
The plugin settings can be global or associated with TeamCity entities, such as a build configuration or a project.

Usually both the state and settings should survive the server restart.

In most cases plugin settings will be serialized and deserialized automatically by the TeamCity server itself; however, to serialize the state, some dedicated code should be provided by the plugin author, except for the cases when the plugin state can be calculated based on other TeamCity entities.
For instance, a plugin whose state depends on build tests can restore its state during the server startup by processing recent builds.

Plugin Settings

Global Settings

In TeamCity it is possible for a plugin to define its settings on a global level. These settings can be stored on a disk in the xml or some other plugin-specific format. The traditional place for storing all global settings is <TeamCity Data Directory>/config.
TeamCity does not provide an API to serialize or deserialize settings under the config directory. It's up to the plugin author to write this code.

To provide a user interface to edit global settings, a plugin can define a custom tab in the Administration area by implementing the AdminPage extension.
See also Web UI Extensions.

Project-level Settings

To associate settings with a project, a plugin can use SProjectFeatureDescriptor.

A project feature is a map of parameters of type String with some id and some type unique among all plugins. If plugin settings can be represented as a map, then project features provide a convenient way of associating plugin settings with a project or project hierarchy. In this case, there is no need to think about serialization or deserialization of settings: they will be saved and restored automatically when the project itself is saved or restored.

Project features can be added/removed at any given point of time with the help of the following methods:

  • addFeature: this method will create SProjectFeatureDescriptor instance and assign an ID to it.
    Note: changes won't be stored on the disk automatically, the persist method must be called to save the project settings on the disk.

  • removeFeature: this method can be used to remove feature from a project, as with the addFeature method, persist must be called to save the settings on the disk.

There are also convenience methods updateFeature to update feature settings without recreating them, as well as different search methods:

To provide a user interface for editing project feature settings, a plugin can define a custom tab by implementing the EditProjectTab extension.
See also Web UI Extensions and TeamCity Invitations plugin.

Associating Files with Project

Sometimes plugin settings are represented as files. For instance, SSH keys or Maven settings are just files and ideally they should be stored as such.
TeamCity provides a directory for each project where files associated with the project can be stored.

Use the getPluginDataDirectory method to access this directory.

The directory location on the disk is: <TeamCity Data Directory>/config/<Project external ID>/pluginData/<plugin name>.It's up to the plugin author to write code to store/remove/modify and delete files under this directory. TeamCity does not provide additional API in this case.

Build Configuration-level Settings

If a plugin needs to associate some settings with a build configuration or template, then it should use Build Features.

In this case, similarly to project features, serialization and deserialization of settings will be performed automatically when a build configuration ortemplate is saved on the disk or restored.

Plugin State

Global State

If a plugin needs to save some state globally, then it should be stored under the <TeamCity Data Directory>/system/pluginData/<plugin name> directory.
A plugin is free to use any serialization mechanism to store its state; TeamCity itself does not provide common utilities to save or access it.

To access the directory containing state of all plugins, use the ServerPaths#getPluginDataDirectory method where ServerPaths is a Spring bean.

Project or Build Configuration-level State

Both SProject and SBuildType provide access to CustomDataStorage.

Custom data storage is a key-value storage with some id. Any map with String keys and values can be stored there. There are no restrictions on the amount of data or length of keys and values, although it is not recommended to store megabytes of data there as it can affect the server performance and memory usage.

An example how a custom data storage can be used with a build configuration (code is similar in case of project):

SBuldType bt = ... CustomDataStorage pluginData = bt.getCustomDataStorage("my-plugin-data"); pluginData.putValue("some key", "some value"); // optionally persist state pluginData.flush()

Custom data storage is persisted into the database automatically with some periodicity or when server is stopped. But if it is important to save the state as soon as possible, then the flush method can be used.

To remove custom data storage, use the dispose method.

Build-level State

If a plugin needs to associate some state with a build, a possible approach is to store the state under the build artifacts directory on the disk. To access this directory, use the SBuild#getArtifactsDirectory method.

By convention, plugins should store their state under the hidden artifacts directory .teamcity/<plugin name>:

SBuild build = ... File artifactsDir = build.getArtifactsDirectory(); File pluginFolder = new File(artifactsDir, jetbrains.buildServer.ArtifactsConstants.TEAMCITY_ARTIFACTS_DIR + File.separatorChar + "myPluginName"); pluginFolder.mkdirs(); ... some code to serialize or deserialize state ...