This documentation explains how to extend Bamboo Specs for your plugins. If you're interested in using Bamboo Specs to manage your Bamboo instance instead, refer to Bamboo Specs documentation.
To begin creating Bamboo Specs for your plugin module, make sure you're familiar with Bamboo Specs documentation and proper plugin module.
Bamboo Specs consists of 3 complementary components to be implemented:
The main part of this tutorial gives general definition and rules which should applied to the mentioned classes. If you want to write a dedicated Bamboo Specs support for a specific plugin module type, refer to the right section of this document:
Properties represent a final form of the module configuration, ready to be used and sent to Bamboo. Properties are immutable objects that hold the state of the module after it had been configured using the correct builder. Here is some key information about task properties:
they are usually hidden from users; user-facing documentation doesn't refer to a properties classes, users should operate on a builder classes
class must implement the EntityProperties interface
class needs to extend the properties base class, e.g. com.atlassian.bamboo.specs.api.model.task.TaskProperties
class should be effectively immutable
class should implement hashCode and equals methods
class names should have Properties suffix
there's a couple of rules which needs to be applied to class constructors:
class must expose one public constructor which is going to be used by builder class. This constructor may accept parameters and initialise fields. It must call validate method in order to validate current configuration
class must expose one parameterless constructor which is going to be used for serialisation/deserialisation, the access may be private, it should initialise default values of the fields and should skip validate method, e.g.
1 2@Immutable public class MyProperties extends TaskProperties { private final String configurationField; private MyProperties() { //private access configurationField = "default value"; //no validate() } public MyProperties(String configurationField) { //public access this.configurationField = configurationField; validate(); } }
all validation code should be enclosed in *validate() *method
Properties should be validated. There are couple of validation rules which should be applied:
com.atlassian.bamboo.specs.api.validators.common.ValidationContext
Specs builders are main classes which will be used by end users to configure Bamboo plans or deployment projects. Builders are mutable entities which should offer seamless configuration of the module, e.g. task or notification.
By design, specs builders provided by Bamboo do not contain the "Builder" suffix in their names, contrary to the general rule of the builder pattern in Java. We recommend you to follow the same pattern. The rationale behind it is to provide the best user experience to users. It's more natural to read/write new Job than new JobBuilder.
Key information about task builders:
builders are end-user-facing, therefore, it's advised that they are properly documented with using JavaDocs
builder should aim to provide the best user experience; if parts of the UI can't be moved directly to builder code, it's better to provide convenient API methods instead of forcibly mimicking UI screens
class needs to extend base builder class, e.g com.atlassian.bamboo.specs.api.builders.task.Task
class must implement *EntityPropertiesBuilder *interface
class should implement a build method which produces properties, it's not forced by the class contract, however, Bamboo Specs export base on existence of this method, you may also consider implementing EntityPropertiesBuilder
interface
class doesn't need to perform validation as it should be handled by the property classes, however it's recommended to validate methods arguments to achieve 'fail-fast' behaviour
default configuration of newly created builder must much the default values which UI screen provides
Additionally, Bamboo team followed the following conventions, which you may consider applying to be consistent with the core builders:
Exporter is a plugin module residing in Bamboo, which is responsible for importing and exporting the properties. Exporter is also responsible for more precise, contextful validation.
Key information about the exporter:
<exporter />
tag in your atlassian-plugin.xmlIn case your component holds any secrets like passwords or SSH and you're using encryption service to properly encrypt them, make sure the task exporter is aware of this and properly imports configuration and does not leak unencrypted secret during export.
Bamboo at the moment doesn't provide any downloadable JAR which would contain a set of Bamboo Specs builders ready to use by end user. It's up to the plugin vendor to properly organise the plugin code and share appropriate modules with end users. We recommend the following structure:
Bamboo provides exportto Bamboo Specs. The feature basically dumps the plan/deployment configuration and uses Bamboo Spec exporter to generate properties classes. Basing on Java class definition and using Java reflection Bamboo generates Bamboo Specs code which can be used to recreate a plan/deployment definition by a user. Here are few rules you should take into consideration when exporting to Bamboo specs:
code generator looks for a builder class basing on the properties → builder naming convention, which means CommandTask will be used CommandTaskProperties; you can instrument code generator by com.atlassian.bamboo.specs.api.codegen.annotations.Builder
annotation and point it to a proper builder name.
1 2@Builder(MyTaskBuilder.class) class MyTaskProperties extends TaskProperties {} class MyTaskBuilder extends Task<MyTaskBuilder, MyTaskProperties> {}
code generator matches setter in builder class with properties field by name if the name doesn't match; you can tweak this behaviour via: com.atlassian.bamboo.specs.api.codegen.annotations.Setter
1 2class MyTaskProperties extends TaskProperties { @Setter("setProperty") String property; } class MyTask extends Task<MyTask, MyTaskProperties> { ... public MyTask setProperty(String property) { this.property = property; return this; } }
if you want to skip code generation for a single field, just use com.atlassian.bamboo.specs.api.codegen.annotations.SkipCodeGen
1 2class MyTaskProperties extends TaskProperties { @SkipCodeGen String property; }
com.atlassian.bamboo.specs.api.codegen.annotations.ConstructFrom
annotation is useful if a builder class doesn't expose public constructor without any parameters
1 2@ConstructFrom({"config1", "config2"}) class MyTaskProperties extends TaskProperties { String config1; String config2; } class MyTask extends Task<MyTask, MyTaskProperties> { public MyTask(String config1, String config2) { this.config1 = config1; this.config2 = config2; } }
if any of the default rules are not applicable to a specific use case, com.atlassian.bamboo.specs.api.codegen.annotations.CodeGenerator annotation can be used to introduce a dedicated code emitter which will take an top level instance of a properties class or any of its field and generate Bamboo Specs builders code
1 2class MavenTaskProperties extends TaskProperties { @CodeGenerator(MavenVersionEmitter.class) protected int version = MavenTask.MAVEN_V3; } class MavenTask extends Task<MavenTask, MavenTaskProperties> { protected int version = MAVEN_V3; public MavenTask version2() { this.version = MAVEN_V2; return this; } public MavenTask version3() { this.version = MAVEN_V3; return this; } } class MavenVersionEmitter implements CodeEmitter<Integer> { public String emitCode(@NotNull final CodeGenerationContext context, @NotNull final Integer value) throws CodeGenerationException { return ".version" + value + "()"; } } @CodeGenerator(MyCodeEmitter.class) class MyTaskProperties extends TaskProperties {} class MyTask extends Task<MyTask, MyTaskProperties> {} class MyCodeEmitter implements CodeEmitter<MyTaskProperties> { public String emitCode(@NotNull final CodeGenerationContext context, @NotNull final MyTaskProperties value) throws CodeGenerationException { //Very specific code emitter for MyTask } }
If Bamboo can't generate code for a particular plugin, it'll export appropriate Java code comment .
Bamboo Specs exporter minimizes the size of its output but omitting fields that are set to their default values. In most cases that simply means skipping the fields that are set to null, empty string or empty collection. For instance, if a Job has no tasks the call to .tasks() method of the Job class will not be generated.
The behavior becomes more complex when non-empty default values are involved. Consider this example:
1 2Plan plan = new Plan(new Project() .key(new BambooKey("PROJECT"), "Plan name", PLANKEY) .enabled(true);
Since the default value of enabled is true, the code above is equivalent of the following, shorter:
1 2Plan plan = new Plan(new Project() .key(new BambooKey("PROJECT"), "Plan name", PLANKEY);
Bamboo Specs exporter produces the latter code thanks to the following procedure:
If a method annotated with @DefaultFieldValues annotation is present, it is used instead of the constructor. Such method:
By following these rules you can ensure that Bamboo Spec exporter produces the shortest possible, yet valid code:
Rule of thumb: it's better if parameterless constructor initialises fields to null/empty than if it uses incorrect default value. In the first case exporter produces some superflous but correct code, in the latter the resulting code might not accurately represent the exported entity.
This section describes adding Bamboo Specs support for your custom task module. Before reading this section, please make sure you're familiar writing custom tasks and you've read the first part of this document. In this tutorial we're assuming that a custom task has already been implemented.
First of all you will need a properties class which will be used as a data model which holds configuration for your task. Follow the common rules to make sure it's compatible with Bamboo Specs. Properties class must implement EntityProperties
interface, however, it's going to be more convenient extend com.atlassian.bamboo.specs.api.model.task.TaskProperties
.
Your task supports one of the following:
If you want to provide support to builds plan or deployment only, override the applicableTo
with a correct value.
CommandTaskProperties.java
1 2@Immutable public final class CommandTaskProperties extends TaskProperties { private static final AtlassianModuleProperties ATLASSIAN_PLUGIN = new AtlassianModuleProperties("com.atlassian.bamboo.plugins.scripttask:task.builder.command"); private static final ValidationContext VALIDATION_CONTEXT = ValidationContext.of("Command task"); @NotNull private final String executable; @Nullable private final String argument; @Nullable private final String environmentVariables; @Nullable private final String workingSubdirectory; // for importing private CommandTaskProperties() { this.executable = null; this.argument = null; this.environmentVariables = null; this.workingSubdirectory = null; } public CommandTaskProperties(@Nullable final String description, final boolean enabled, @NotNull final String executable, @Nullable final String argument, @Nullable final String environmentVariables, @Nullable final String workingSubdirectory) throws PropertiesValidationException { super(description, enabled); this.executable = executable; this.argument = argument; this.environmentVariables = environmentVariables; this.workingSubdirectory = workingSubdirectory; validate(); } @Override public void validate() throws PropertiesValidationException { super.validate(); checkThat(VALIDATION_CONTEXT, StringUtils.isNotBlank(executable), "Executable is not defined"); } // Equals and hash code // ... // Getters // ... }
Once you have a properties class, you will need a builder class which is used to build it. Make sure your builder class fulfils common builder rules. Consider extending abstract com.atlassian.bamboo.specs.api.builders.task.Task
which should help in implementing your builder.
CommandTask.java
1 2/** * Represents a task that executes a command. */ public class CommandTask extends Task<CommandTask, CommandTaskProperties> { @NotNull private String executable; @Nullable private String argument; @Nullable private String environmentVariables; @Nullable private String workingSubdirectory; /** * Sets label (<em>not a path</em>) of command to be executed. This label must be first * defined in the GUI on the Administration/Executables page. */ public CommandTask executable(@NotNull final String executable) { checkNotEmpty("executable", executable); this.executable = executable; return this; } /** * Sets command line argument to be passed when command is executed. */ public CommandTask argument(@Nullable final String argument) { this.argument = argument; return this; } /** * Sets environment variables to be set when command is executed. */ public CommandTask environmentVariables(@Nullable final String environmentVariables) { this.environmentVariables = environmentVariables; return this; } /** * Sets a directory the command should be executed in. */ public CommandTask workingSubdirectory(@Nullable final String workingSubdirectory) { this.workingSubdirectory = workingSubdirectory; return this; } @NotNull @Override protected CommandTaskProperties build() { return new CommandTaskProperties( description, taskEnabled, executable, argument, environmentVariables, workingSubdirectory); } }
The last step is to write exporter class to implement logic responsible for importing and exporting your task's configuration. Note that the previous components were a part of Bamboo Specs and they are directly (builders) or indirectly (properties) used by users. An exporter is a part of Bamboo and it's required to have it bundled in your plugin JAR. An exporter consists of two elements:
com.atlassian.bamboo.task.export.TaskDefinitionExporter
interface<exporter/>
entry in your task's plugin module definition (atlassian-plugin.xml
) Note that an exporter works in the Bamboo context, which means it has access to all OSGI services Bamboo exports. You can use it to implement more in depth validation e.g. while there's no way to validate if an executable label provided by user is valid on the properties class level, it is possible to do it in the exporter.
CommandTaskExporter.java
1 2public class CommandTaskExporter implements TaskDefinitionExporter { private static final ValidationContext COMMAND_CONTEXT = ValidationContext.of("Command task"); @Autowired private UIConfigSupport uiConfigSupport; @NotNull @Override public CommandTask toSpecsEntity(@NotNull final TaskDefinition taskDefinition) { final Map<String, String> configuration = taskDefinition.getConfiguration(); return new CommandTask() .executable(configuration.get(CommandConfig.CFG_SCRIPT)) .argument(configuration.getOrDefault(CommandConfig.CFG_ARGUMENT, null)) .environmentVariables(configuration.getOrDefault(TaskConfigConstants.CFG_ENVIRONMENT_VARIABLES, null)) .workingSubdirectory(configuration.getOrDefault(TaskConfigConstants.CFG_WORKING_SUBDIRECTORY, null)); } @Override public Map<String, String> toTaskConfiguration(@NotNull TaskContainer taskContainer, final TaskProperties taskProperties) { final CommandTaskProperties commandTaskProperties = Narrow.downTo(taskProperties, CommandTaskProperties.class); Preconditions.checkState(commandTaskProperties != null, "Don't know how to import task properties of type: " + taskProperties.getClass().getName()); final Map<String, String> cfg = new HashMap<>(); cfg.put(CommandConfig.CFG_SCRIPT, commandTaskProperties.getExecutable()); if (commandTaskProperties.getArgument() != null) cfg.put(CommandConfig.CFG_ARGUMENT, commandTaskProperties.getArgument()); if (commandTaskProperties.getEnvironmentVariables() != null) cfg.put(CommandConfig.CFG_ENVIRONMENT_VARIABLES, commandTaskProperties.getEnvironmentVariables()); if (commandTaskProperties.getWorkingSubdirectory() != null) cfg.put(TaskConfigConstants.CFG_WORKING_SUBDIRECTORY, commandTaskProperties.getWorkingSubdirectory()); return cfg; } @Override public List<ValidationProblem> validate(@NotNull TaskValidationContext taskValidationContext, @NotNull TaskProperties taskProperties) { final List<ValidationProblem> validationProblems = new ArrayList<>(); final CommandTaskProperties commandTaskProperties = Narrow.downTo(taskProperties, CommandTaskProperties.class); if (commandTaskProperties != null) { final List<String> labels = uiConfigSupport.getExecutableLabels(CommandConfig.CAPABILITY_SHORT_KEY); final String label = commandTaskProperties.getExecutable(); if (labels == null || !labels.contains(label)) { validationProblems.add(new ValidationProblem( COMMAND_CONTEXT, "Can't find executable by label: '" + label + "'. Available values: " + labels)); } } return result; } }
atlassian-plugin.xml
1 2<atlassian-plugin ...> <taskType key="task.builder.command" name="Command" class="com.atlassian.bamboo.plugins.command.task.CommandBuildTask"> ... <exporter class="com.atlassian.bamboo.plugins.command.task.export.CommandTaskExporter"/> </taskType> </atlassian-plugin>
Even without a proper builder, properties and exporter classes, it's possible to use Bamboo Specs to import custom tasks. The com.atlassian.bamboo.specs.api.builders.task.AnyTask
can be used instead of a dedicated com.atlassian.bamboo.specs.api.builders.task.Task
implementation, for example when one is not yet implemented.
The * AnyTask* is a generic specs builder which accepts two main configuration options: Atlassian plugin module key of the task, and tasks configuration map.
The plugin module key is the key of the task that's to be imported. It should match your custom task module key - it's build from Atlassian plugin key and the plugin module key, e.g. the Ant task type is recognised by the com.atlassian.bamboo.plugins.ant:task.builder.ant
1 2<atlassian-plugin key="com.atlassian.bamboo.plugins.ant" name="${pom.name}" pluginsVersion="1"> ... <taskType key="task.builder.ant" name="Ant" class="com.atlassian.bamboo.plugins.ant.task.AntBuildTask"> ... </taskType> ... </atlassian-plugin>
The configuration map is simple task configuration
Generally speaking AnyTask should be used as a last resort and dedicated builders should be favoured, because:
This section describes adding Bamboo Specs support for your custom trigger reason module. Before reading this section, make sure you're familiar writing trigger reason plugin module and you've read the first part of this document. In this tutorial we're assuming that a trigger has already been implemented.
First of all you will need a properties class which will be used as a data model which holds configuration for your task. Follow the common rules to make sure it's compatible for Bamboo Specs. Properties class must implement EntityProperties
interface, however, it's going to be more convenient extend com.atlassian.bamboo.specs.api.model.trigger.TriggerProperties
. However, if your tigger cooperates with any repository via triggering repositories it's better if you use com.atlassian.bamboo.specs.api.model.trigger.RepositoryBasedTriggerProperties
as a base class. Otherwise the triggering repositories might not be imported properly.
Your trigger supports one of the following:
applicableTo
with a correct value.RepositoryPollingTriggerProperties.java
1 2@Immutable public final class RepositoryPollingTriggerProperties extends RepositoryBasedTriggerProperties { public static final String MODULE_KEY = "com.atlassian.bamboo.triggers.atlassian-bamboo-triggers:poll"; private static final String NAME = "Repository polling"; private static final AtlassianModuleProperties MODULE = EntityPropertiesBuilders.build(new AtlassianModule(MODULE_KEY)); private static final int DEFAULT_POLLING_PERIOD = preventInlining(180); private final Duration pollingPeriod; private final String cronExpression; private final PollType pollType; private RepositoryPollingTriggerProperties() { super(); pollingPeriod = Duration.ofSeconds(DEFAULT_POLLING_PERIOD); cronExpression = null; pollType = PollType.PERIOD; } public RepositoryPollingTriggerProperties(final String description, final boolean isEnabled, final TriggeringRepositoriesType triggeringRepositoriesType, final List<VcsRepositoryIdentifierProperties> triggeringRepositories, final String cronExpression) { super(NAME, description, isEnabled, triggeringRepositoriesType, triggeringRepositories); this.cronExpression = cronExpression; this.pollingPeriod = null; pollType = PollType.CRON; validate(); } public RepositoryPollingTriggerProperties(final String description, final boolean isEnabled, final TriggeringRepositoriesType triggeringRepositoriesType, final List<VcsRepositoryIdentifierProperties> selectedTriggeringRepositories, final Duration pollingPeriod) { super(NAME, description, isEnabled, triggeringRepositoriesType, selectedTriggeringRepositories); this.cronExpression = null; this.pollingPeriod = pollingPeriod; pollType = PollType.PERIOD; validate(); } @Override public void validate() { super.validate(); if (pollType == null) { throw new PropertiesValidationException("Can't create repository polling trigger without any polling type"); } switch (pollType) { case PERIOD: checkNotNull("pollingPeriod", pollingPeriod); checkPositive("pollingPeriod", (int) pollingPeriod.getSeconds()); break; case CRON: CronExpressionClientSideValidator.validate(cronExpression); break; default: throw new PropertiesValidationException("Can't create repository polling trigger - unknown polling type: " + pollType); } } public enum PollType { PERIOD, CRON } // Equals and hash code // ... // Getters // ... }
Once you have a properties class you will need a builder class which is used to build it. Make sure your builder class fulfils common builder rules. Consider extending abstract com.atlassian.bamboo.specs.api.builders.trigger.Trigger
which should help in implementing your builder. However, com.atlassian.bamboo.specs.api.builders.trigger.RepositoryBasedTrigger
might be better suited if your trigger cooperates with triggering repositories.
RepositoryPollingTrigger.java
1 2/** * Represents repository polling trigger. */ public class RepositoryPollingTrigger extends RepositoryBasedTrigger<RepositoryPollingTrigger, RepositoryPollingTriggerProperties> { private static final EnumSet<TimeUnit> APPLICABLE_TIME_UNITS = EnumSet.of(TimeUnit.SECONDS, TimeUnit.MINUTES, TimeUnit.HOURS, TimeUnit.DAYS); private RepositoryPollingTriggerProperties.PollType pollType; private Duration pollingPeriod; private String cronExpression; /** * Creates repository polling trigger. */ public RepositoryPollingTrigger() { triggeringRepositoriesType = TriggeringRepositoriesType.ALL; pollingPeriod = Duration.ofSeconds(180); cronExpression = null; pollType = RepositoryPollingTriggerProperties.PollType.PERIOD; } /** * Specifies how often (in {@link TimeUnit}) Bamboo should check the repository for changes. * Time units smaller than {@link TimeUnit#SECONDS} won't be accepted. * Default value is 180 seconds. * * @see #withPollingPeriod(Duration) */ public RepositoryPollingTrigger pollEvery(int every, @NotNull TimeUnit timeUnit) { checkNotNull("timeUnit", timeUnit); checkPositive("pollingPeriod", every); checkArgument(ValidationContext.empty(), APPLICABLE_TIME_UNITS.contains(timeUnit), "Polling is available only with seconds, minutes and hours based period"); pollingPeriod = Duration.ofSeconds(timeUnit.toSeconds(every)); pollType = PERIOD; cronExpression = null; return this; } /** * Specifies time interval between checks for changes in the repositories. * Duration smaller than a second won't be accepted. * Default value is 180 seconds. * * @see #pollEvery(int, TimeUnit) */ public RepositoryPollingTrigger withPollingPeriod(@NotNull Duration duration) { checkNotNull("duration", duration); checkArgument(ValidationContext.empty(), duration.getSeconds() > 0, "Polling interval cannot be shorted than one second"); pollingPeriod = duration; pollType = PERIOD; cronExpression = null; return this; } /** * Selects polling type for this trigger. Possible values: * <dl> * <dt>PERIOD</dt> * <dd>Poll in defined intervals.</dd> * <dt>CRON</dt> * <dd>Poll according to cron expression.</dd> * </dl> */ public RepositoryPollingTrigger withPollType(@NotNull RepositoryPollingTriggerProperties.PollType pollType) { checkNotNull("pollType", pollType); this.pollType = pollType; return this; } /** * Orders Bamboo to check repository for changes once daily at specified time. */ public RepositoryPollingTrigger pollOnceDaily(@NotNull LocalTime at) { return pollWithCronExpression(CronExpressionCreationHelper.scheduleOnceDaily(at)); } /** * Orders Bamboo to check repository for changes weekly at specified days of week and time. */ public RepositoryPollingTrigger pollWeekly(@NotNull LocalTime at, DayOfWeek... onDays) { return pollWithCronExpression(CronExpressionCreationHelper.scheduleWeekly(at, onDays)); } /** * Orders Bamboo to check repository for changes weekly at specified days of week and time. */ public RepositoryPollingTrigger pollWeekly(@NotNull LocalTime at, @NotNull Collection<DayOfWeek> days) { return pollWithCronExpression(CronExpressionCreationHelper.scheduleWeekly(at, days)); } /** * Orders Bamboo to check repository for changes once monthly at specified day of month and time. */ public RepositoryPollingTrigger pollMonthly(@NotNull LocalTime at, int dayOfMonth) { return pollWithCronExpression(CronExpressionCreationHelper.scheduleMonthly(at, dayOfMonth)); } /** * Orders Bamboo to check repository for changes based on given cron expression. */ public RepositoryPollingTrigger pollWithCronExpression(@NotNull String cronExpression) { checkNotBlank("cronExpression", cronExpression); this.cronExpression = cronExpression; pollType = CRON; this.pollingPeriod = null; return this; } @Override protected RepositoryPollingTriggerProperties build() { switch (pollType) { case PERIOD: return new RepositoryPollingTriggerProperties(description, triggerEnabled, triggeringRepositoriesType, selectedTriggeringRepositories, pollingPeriod); case CRON: return new RepositoryPollingTriggerProperties(description, triggerEnabled, triggeringRepositoriesType, selectedTriggeringRepositories, cronExpression); default: throw new AssertionError(String.format("Don't know what %s is.", pollType)); } } }
The last step is to write an exporter class to implement logic responsible for importing and exporting your trigger's configuration. Note that previous components were part of Bamboo Specs and they are directly (builders) or indirectly (properties) used by users. An exporter is a part of Bamboo and it's required to have it bundled in your plugin JAR. Exporters consist of two elements:
com.atlassian.bamboo.trigger.export.TriggerDefinitionExporter
interface<exporter/>
entry in your trigger's plugin module definition (atlassian-plugin.xml
) Please note, exporter works in the Bamboo context, which means it has access to all OSGI services Bamboo exports. You can use it to implement more in depth validation. Please note, the exporter must import only the configuration of your trigger. If you're using triggering repositories you should use RepositoryBasedTriggerProperties as a base class. Bamboo will detect such situation and properly import the repositories ids.
CommandTaskExporter.java
1 2public class RepositoryPollingTriggerDefinitionExporter implements TriggerDefinitionExporter { static final String TRIGGER_CRON_EXPRESSION_KEY = "repository.change.poll.cronExpression"; static final String TRIGGER_PERIOD_KEY = "repository.change.poll.pollingPeriod"; static final String TRIGGER_POLL_TYPE_KEY = "repository.change.poll.type"; private final ValidationContext validationContext = ValidationContext.of("Repository polling trigger"); @NotNull @Override public Map<String, String> toTriggerConfiguration(@NotNull TriggerProperties triggerProperties, @NotNull Triggerable triggerable) { final RepositoryPollingTriggerProperties trigger = Narrow.downTo(triggerProperties, RepositoryPollingTriggerProperties.class); checkArgument(validationContext, trigger != null, "Don't know how to handle: " + triggerProperties.getClass()); final Map<String, String> configuration = Maps.newHashMap(); configuration.put(TRIGGER_POLL_TYPE_KEY, trigger.getPollType().toString()); switch (trigger.getPollType()) { case PERIOD: configuration.put(TRIGGER_PERIOD_KEY, String.valueOf(trigger.getPollingPeriod().getSeconds())); break; case CRON: configuration.put(TRIGGER_CRON_EXPRESSION_KEY, String.valueOf(trigger.getCronExpression())); break; } return configuration; } @NotNull @Override public Trigger toSpecsEntity(@NotNull TriggerDefinition triggerDefinition) { RepositoryPollingTrigger repositoryPollingTrigger = new RepositoryPollingTrigger(); RepositoryPollingTriggerProperties.PollType pollType = RepositoryPollingTriggerProperties.PollType .valueOf(triggerDefinition.getConfiguration().get(TRIGGER_POLL_TYPE_KEY)); switch (pollType) { case CRON: repositoryPollingTrigger.pollWithCronExpression(triggerDefinition.getConfiguration().get(TRIGGER_CRON_EXPRESSION_KEY)); break; case PERIOD: repositoryPollingTrigger.pollEvery(Integer.parseInt(triggerDefinition.getConfiguration().get(TRIGGER_PERIOD_KEY)), TimeUnit.SECONDS); break; } return repositoryPollingTrigger; } @Override public List<ValidationProblem> validate(@NotNull TriggerValidationContext triggerValidationContext, @NotNull TriggerProperties triggerProperties) { List<ValidationProblem> problems = Lists.newArrayList(); RepositoryPollingTriggerProperties trigger = Narrow.downTo(triggerProperties, RepositoryPollingTriggerProperties.class); if (trigger != null) { switch (trigger.getPollType()) { case CRON: if (!CronExpression.isValidExpression(trigger.getCronExpression())) { problems.add(new ValidationProblem(String.format("The cron expresion: %s is not valid", trigger.getCronExpression()))); } break; //no specific server side validation for PERIOD poll type } RepositoryTriggersValidator.validateRepositoryTriggers(triggerValidationContext, trigger, problems); } else { problems.add(new ValidationProblem("Don't know how to validate " + triggerProperties.getClass())); } return problems; } }
atlassian-plugin.xml
1 2<atlassian-plugin ...> ... <triggerType key="poll" name="Repository polling" class="com.atlassian.bamboo.trigger.polling.PollingTriggerActivator"> ... <exporter class="com.atlassian.bamboo.trigger.exporters.RepositoryPollingTriggerDefinitionExporter"/> </triggerType> </atlassian-plugin>
Even without a proper builder, properties and exporter classes, it's possible to use Bamboo Specs to import custom triggers. The com.atlassian.bamboo.specs.api.builders.trigger.AnyTrigger
can be used instead of a dedicated com.atlassian.bamboo.specs.api.builders.trigger.Trigger
implementation, for example when one is not yet implemented.
The * AnyTrigger* is a generic specs builder which accepts two main configuration options: Atlassian plugin module key of the trigger, and the trigger's configuration map.
The plugin module key is the key of the task that's to be imported. It should match your custom task module key, it's build from Atlassian plugin key and the plugin module key, e.g. the polling trigger type is recognised by the com.atlassian.bamboo.triggers.atlassian-bamboo-triggers:poll
1 2<atlassian-plugin key="com.atlassian.bamboo.triggers.atlassian-bamboo-triggers" ...> ... <triggerType key="poll" name="Repository polling" class="com.atlassian.bamboo.trigger.polling.PollingTriggerActivator"> ... </triggerType> ... </atlassian-plugin>
The configuration map simply represents a trigger's configuration
Generally speaking AnyTrigger should be used as a last resort and dedicated builders should be favoured, because:
Rate this page: