Skip to content

Commit 9900c97

Browse files
committed
[JENKINS-74913] Allow extension point in bitbucket source plugin to provide a implementation for web-hooks management
new interface to apply a webhook configuration to the Bitbucket
1 parent e20e512 commit 9900c97

File tree

12 files changed

+148
-45
lines changed

12 files changed

+148
-45
lines changed

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/webhook/BitbucketWebhookConfiguration.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,16 @@
2929
import edu.umd.cs.findbugs.annotations.NonNull;
3030
import hudson.model.Describable;
3131
import jenkins.model.Jenkins;
32+
import org.kohsuke.accmod.Restricted;
33+
import org.kohsuke.accmod.restrictions.Beta;
3234

3335
/**
3436
* The implementation represents an a webhook configuration that can be used in
3537
* a {@link BitbucketEndpoint}.
3638
*
3739
* @since 937.0.0
3840
*/
41+
@Restricted(Beta.class)
3942
public interface BitbucketWebhookConfiguration extends Describable<BitbucketWebhookConfiguration> {
4043

4144
/**
@@ -88,9 +91,8 @@ public interface BitbucketWebhookConfiguration extends Describable<BitbucketWebh
8891
* Returns the implementation that is in charge to apply this configuration to the Bitbucket.
8992
* @param <T> the specific BitbucketWebhookIntegration subtype.
9093
* @return a new instance of the integration, never return a singleton instance.
91-
* FIXME think if returns class and in case instantiate via reflection and apply this configuration to ensure NO singleton.
9294
*/
93-
<T extends BitbucketWebhookIntegration> T getIntegration();
95+
Class<? extends BitbucketWebhookIntegration> getIntegration();
9496

9597
/**
9698
* @see Describable#getDescriptor()

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/api/webhook/BitbucketWebhookIntegration.java

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,105 @@
55
import hudson.ExtensionPoint;
66
import java.io.IOException;
77
import java.util.Collection;
8+
import jenkins.scm.api.SCMSource;
89
import jenkins.scm.api.trait.SCMSourceTrait;
10+
import org.kohsuke.accmod.Restricted;
11+
import org.kohsuke.accmod.restrictions.Beta;
912

13+
/**
14+
* The implementation is in charge to apply a specific
15+
* {@link BitbucketWebhookConfiguration} to the Bitbucket registering the
16+
* webhook commit
17+
*/
18+
@Restricted(Beta.class)
1019
public interface BitbucketWebhookIntegration extends ExtensionPoint {
1120

21+
/**
22+
* The owner of the repository where register the webhook.
23+
*
24+
* @param repositoryOwner name
25+
*/
1226
void setRepositoryOwner(@NonNull String repositoryOwner);
27+
28+
/**
29+
* Name of the repository where register the webhook.
30+
*
31+
* @param repositoryName
32+
*/
1333
void setRepositoryName(@NonNull String repositoryName);
34+
35+
/**
36+
* The base URL of endpoint of the Bitbucket host.
37+
*
38+
* @param serverURL the base of the endpoint to call.
39+
*/
1440
void setServerURL(@NonNull String serverURL);
41+
42+
/**
43+
* The callback URL where send event payload.
44+
* <p>
45+
* The method is called with the URL of the default receiver and processed
46+
* using an appropriate {@link BitbucketWebhookProcessor}. The
47+
* implementation could decide to ignore given URL and use an own servlet
48+
* endpoint to process own events.
49+
*
50+
* @param callbackURL used to send webhook payload.
51+
*/
1552
void setCallbackURL(@NonNull String callbackURL);
1653

54+
/**
55+
* The configuration that returned this implementation class.
56+
*
57+
* @param configuration to apply
58+
*/
59+
void apply(BitbucketWebhookConfiguration configuration);
60+
61+
/**
62+
* A list of traits class that this integration supports to gather extra
63+
* configuration options.
64+
*
65+
* @return a list of {@link SCMSourceTrait} classes.
66+
*/
1767
Collection<Class<? extends SCMSourceTrait>> supportedTraits();
68+
69+
/**
70+
* Trait instance associate to a {@link SCMSource} where gather extra
71+
* configuration options.
72+
* <p>
73+
* Each {@link BitbucketWebhookConfiguration} that would gather
74+
* configuration options customised at project level must provide an own
75+
* specific trait.
76+
*
77+
* @param trait to apply
78+
*/
1879
void apply(SCMSourceTrait trait);
1980

20-
Collection<BitbucketWebHook> retrieve(@NonNull BitbucketWebhookClient client) throws IOException;
81+
/**
82+
* Returns the list of all registered webhook at this repository related to
83+
* this Jenkins.
84+
*
85+
* @param client authenticated to communicate with Bitbucket
86+
* @return a list of registered {@link BitbucketWebHook}.
87+
* @throws IOException in case of communication issue with Bitbucket
88+
*/
89+
Collection<BitbucketWebHook> read(@NonNull BitbucketWebhookClient client) throws IOException;
90+
91+
/**
92+
* Save a webhook (updating or creating a new one) using the actual
93+
* configuration.
94+
*
95+
* @param client authenticated to communicate with Bitbucket
96+
* @throws IOException in case of communication issue with Bitbucket
97+
*/
2198
void register(@NonNull BitbucketWebhookClient client) throws IOException;
22-
void remove(@NonNull BitbucketWebHook payload, @NonNull BitbucketWebhookClient client) throws IOException;
99+
100+
/**
101+
* Remove the webhook from the Bitbucket repository with the given
102+
* identifier.
103+
*
104+
* @param webhookId webhook identifier to delete.
105+
* @param client authenticated to communicate with Bitbucket
106+
* @throws IOException in case of communication issue with Bitbucket
107+
*/
108+
void remove(@NonNull String webhookId, @NonNull BitbucketWebhookClient client) throws IOException;
23109
}

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/hooks/WebhookAutoRegisterListener.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import edu.umd.cs.findbugs.annotations.CheckForNull;
4040
import edu.umd.cs.findbugs.annotations.NonNull;
4141
import hudson.Extension;
42+
import hudson.ExtensionList;
4243
import hudson.Util;
4344
import hudson.model.Item;
4445
import hudson.model.listeners.ItemListener;
@@ -174,8 +175,9 @@ private synchronized void registerHooks(SCMSourceOwner owner) throws IOException
174175
private BitbucketWebhookIntegration buildWebhookIntegration(BitbucketSCMSource source, BitbucketEndpoint endpoint) {
175176
BitbucketWebhookConfiguration webhookConfig = endpoint.getWebhook();
176177

177-
BitbucketWebhookIntegration integration = webhookConfig.getIntegration();
178+
BitbucketWebhookIntegration integration = ExtensionList.lookupFirst(webhookConfig.getIntegration());
178179
// setup the integration with base required information
180+
integration.apply(webhookConfig);
179181
integration.setServerURL(endpoint.getServerURL());
180182
integration.setRepositoryOwner(source.getRepoOwner());
181183
integration.setRepositoryName(source.getRepository());
@@ -219,7 +221,7 @@ private void removeHooks(SCMSourceOwner owner) throws IOException {
219221
}
220222

221223
BitbucketWebhookIntegration integration = buildWebhookIntegration(source, endpoint);
222-
Collection<BitbucketWebHook> webhooks = integration.retrieve(webhookClient)
224+
Collection<BitbucketWebHook> webhooks = integration.read(webhookClient)
223225
.stream()
224226
.filter(hook -> hook.getUrl().startsWith(getCallbackRootURL(endpoint.getWebhook())))
225227
.toList();
@@ -228,7 +230,7 @@ private void removeHooks(SCMSourceOwner owner) throws IOException {
228230
if (hook != null && !isUsedSomewhereElse(owner, source.getRepoOwner(), source.getRepository())) {
229231
logger.log(Level.INFO, "Removing hook for {0}/{1}",
230232
new Object[] { source.getRepoOwner(), source.getRepository() });
231-
integration.remove(hook, webhookClient);
233+
integration.remove(hook.getUuid(), webhookClient);
232234
} else {
233235
logger.log(Level.FINE, "NOT removing hook for {0}/{1} because does not exists or its used in other project",
234236
new Object[] { source.getRepoOwner(), source.getRepository() });

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/AbstractBitbucketWebhookConfiguration.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ public boolean isEnableHookSignature() {
120120
}
121121

122122
@Override
123+
@CheckForNull
123124
public String getEndpointJenkinsRootURL() {
124125
return endpointJenkinsRootURL;
125126
}

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/CloudWebhookConfiguration.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.cloud;
2525

2626
import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.EndpointType;
27+
import com.cloudbees.jenkins.plugins.bitbucket.api.webhook.BitbucketWebhookIntegration;
2728
import com.cloudbees.jenkins.plugins.bitbucket.impl.endpoint.BitbucketCloudEndpoint;
2829
import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketCredentialsUtils;
2930
import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.AbstractBitbucketWebhookConfiguration;
@@ -64,10 +65,9 @@ public String getId() {
6465
return "CLOUD";
6566
}
6667

67-
@SuppressWarnings("unchecked")
6868
@Override
69-
public CloudWebhookIntegration getIntegration() {
70-
return new CloudWebhookIntegration(this);
69+
public Class<? extends BitbucketWebhookIntegration> getIntegration() {
70+
return CloudWebhookIntegration.class;
7171
}
7272

7373
@Symbol("cloudWebhook")

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/cloud/CloudWebhookIntegration.java

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketWebHook;
2727
import com.cloudbees.jenkins.plugins.bitbucket.api.webhook.BitbucketWebhookClient;
28+
import com.cloudbees.jenkins.plugins.bitbucket.api.webhook.BitbucketWebhookConfiguration;
2829
import com.cloudbees.jenkins.plugins.bitbucket.api.webhook.BitbucketWebhookIntegration;
2930
import com.cloudbees.jenkins.plugins.bitbucket.client.BitbucketCloudPage;
3031
import com.cloudbees.jenkins.plugins.bitbucket.client.repository.BitbucketCloudHook;
@@ -73,10 +74,6 @@ public class CloudWebhookIntegration implements BitbucketWebhookIntegration {
7374
private String repositoryName;
7475
private String callbackURL;
7576

76-
public CloudWebhookIntegration(@NonNull CloudWebhookConfiguration configuration) {
77-
this.configuration = configuration;
78-
}
79-
8077
@Override
8178
public Collection<Class<? extends SCMSourceTrait>> supportedTraits() {
8279
return Collections.emptyList();
@@ -87,6 +84,11 @@ public void apply(SCMSourceTrait configurationTrait) {
8784
// nothing to configure
8885
}
8986

87+
@Override
88+
public void apply(BitbucketWebhookConfiguration configuration) {
89+
this.configuration = (CloudWebhookConfiguration) configuration;
90+
}
91+
9092
@NonNull
9193
public String getRepositoryOwner() {
9294
return repositoryOwner;
@@ -124,7 +126,7 @@ public void setCallbackURL(@NonNull String callbackURL) {
124126

125127
@Override
126128
@NonNull
127-
public Collection<BitbucketWebHook> retrieve(@NonNull BitbucketWebhookClient client) throws IOException {
129+
public Collection<BitbucketWebHook> read(@NonNull BitbucketWebhookClient client) throws IOException {
128130
String url = UriTemplate.fromTemplate(WEBHOOK_URL + "{?page,pagelen}")
129131
.set("owner", repositoryOwner)
130132
.set("repo", repositoryName)
@@ -210,20 +212,21 @@ private void update(@NonNull BitbucketCloudHook payload, @NonNull BitbucketWebho
210212
}
211213

212214
@Override
213-
public void remove(@NonNull BitbucketWebHook payload, @NonNull BitbucketWebhookClient client) throws IOException {
215+
public void remove(@NonNull String webhookId, @NonNull BitbucketWebhookClient client) throws IOException {
214216
String url = UriTemplate
215217
.fromTemplate(WEBHOOK_URL + "/{hook}")
216218
.set("owner", repositoryOwner)
217219
.set("repo", repositoryName)
218-
.set("hook", payload.getUuid())
220+
.set("hook", webhookId)
219221
.expand();
220222
client.delete(url);
221223
}
222224

223225
@Override
224226
public void register(@NonNull BitbucketWebhookClient client) throws IOException {
225-
BitbucketCloudHook existingHook = (BitbucketCloudHook) retrieve(client)
227+
BitbucketCloudHook existingHook = (BitbucketCloudHook) read(client)
226228
.stream()
229+
// FIXME the endpoint should provide the default
227230
.filter(hook -> hook.getUrl().startsWith(configuration.getEndpointJenkinsRootURL()))
228231
.findFirst()
229232
.orElse(null);

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginWebhookConfiguration.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.EndpointType;
2727
import com.cloudbees.jenkins.plugins.bitbucket.api.webhook.BitbucketWebhookConfiguration;
2828
import com.cloudbees.jenkins.plugins.bitbucket.api.webhook.BitbucketWebhookDescriptor;
29+
import com.cloudbees.jenkins.plugins.bitbucket.api.webhook.BitbucketWebhookIntegration;
2930
import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketCredentialsUtils;
3031
import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.Messages;
3132
import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.server.ServerWebhookConfiguration;
@@ -130,10 +131,9 @@ public String getId() {
130131
return "PLUGIN";
131132
}
132133

133-
@SuppressWarnings("unchecked")
134134
@Override
135-
public PluginWebhookIntegration getIntegration() {
136-
return new PluginWebhookIntegration(this);
135+
public Class<? extends BitbucketWebhookIntegration> getIntegration() {
136+
return PluginWebhookIntegration.class;
137137
}
138138

139139
@Symbol("pluginWebhook")

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/plugin/PluginWebhookIntegration.java

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketWebHook;
2727
import com.cloudbees.jenkins.plugins.bitbucket.api.webhook.BitbucketWebhookClient;
28+
import com.cloudbees.jenkins.plugins.bitbucket.api.webhook.BitbucketWebhookConfiguration;
2829
import com.cloudbees.jenkins.plugins.bitbucket.api.webhook.BitbucketWebhookIntegration;
2930
import com.cloudbees.jenkins.plugins.bitbucket.impl.util.JsonParser;
3031
import com.cloudbees.jenkins.plugins.bitbucket.server.client.repository.BitbucketPluginWebhook;
@@ -77,10 +78,6 @@ public class PluginWebhookIntegration implements BitbucketWebhookIntegration {
7778
private String callbackURL;
7879
private String committersToIgnore;
7980

80-
public PluginWebhookIntegration(@NonNull PluginWebhookConfiguration configuration) {
81-
this.configuration = configuration;
82-
}
83-
8481
@Override
8582
public Collection<Class<? extends SCMSourceTrait>> supportedTraits() {
8683
return List.of(WebhookConfigurationTrait.class);
@@ -93,6 +90,11 @@ public void apply(SCMSourceTrait trait) {
9390
}
9491
}
9592

93+
@Override
94+
public void apply(BitbucketWebhookConfiguration configuration) {
95+
this.configuration = (PluginWebhookConfiguration) configuration;
96+
}
97+
9698
@NonNull
9799
public String getRepositoryOwner() {
98100
return repositoryOwner;
@@ -139,7 +141,7 @@ private String getCallbackURL() {
139141

140142
@Override
141143
@NonNull
142-
public Collection<BitbucketWebHook> retrieve(@NonNull BitbucketWebhookClient client) throws IOException {
144+
public Collection<BitbucketWebHook> read(@NonNull BitbucketWebhookClient client) throws IOException {
143145
String url = UriTemplate.fromTemplate(serverURL + WEBHOOK_API)
144146
.set("owner", repositoryOwner)
145147
.set("repo", repositoryName)
@@ -213,18 +215,18 @@ private void update(@NonNull BitbucketPluginWebhook payload, @NonNull BitbucketW
213215
}
214216

215217
@Override
216-
public void remove(@NonNull BitbucketWebHook payload, @NonNull BitbucketWebhookClient client) throws IOException {
218+
public void remove(@NonNull String webhookId, @NonNull BitbucketWebhookClient client) throws IOException {
217219
String url = UriTemplate.fromTemplate(serverURL + WEBHOOK_API)
218220
.set("owner", repositoryOwner)
219221
.set("repo", repositoryName)
220-
.set("id", payload.getUuid())
222+
.set("id", webhookId)
221223
.expand();
222224
client.delete(url);
223225
}
224226

225227
@Override
226228
public void register(@NonNull BitbucketWebhookClient client) throws IOException {
227-
BitbucketPluginWebhook existingHook = (BitbucketPluginWebhook) retrieve(client)
229+
BitbucketPluginWebhook existingHook = (BitbucketPluginWebhook) read(client)
228230
.stream()
229231
// FIXME !! the endpoint could be null must rely on default !!
230232
.filter(hook -> hook.getUrl().startsWith(configuration.getEndpointJenkinsRootURL()))

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/webhook/server/ServerWebhookConfiguration.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
package com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.server;
2525

2626
import com.cloudbees.jenkins.plugins.bitbucket.api.endpoint.EndpointType;
27+
import com.cloudbees.jenkins.plugins.bitbucket.api.webhook.BitbucketWebhookIntegration;
2728
import com.cloudbees.jenkins.plugins.bitbucket.impl.util.BitbucketCredentialsUtils;
2829
import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.AbstractBitbucketWebhookConfiguration;
2930
import com.cloudbees.jenkins.plugins.bitbucket.impl.webhook.Messages;
@@ -67,10 +68,9 @@ public String getId() {
6768
return "NATIVE";
6869
}
6970

70-
@SuppressWarnings("unchecked")
7171
@Override
72-
public ServerWebhookIntegration getIntegration() {
73-
return new ServerWebhookIntegration(this);
72+
public Class<? extends BitbucketWebhookIntegration> getIntegration() {
73+
return ServerWebhookIntegration.class;
7474
}
7575

7676
@Symbol("serverWebhook")

0 commit comments

Comments
 (0)