Repository: buildbot/buildbot

Issue:
Bitbucket cloud status update returns 400 for status_key longer than 40 characters
When using a status_key for reporters.BitbucketStatusPush, there is no check whether it is longer than 40 characters, which appears to be the limit that Bitbucket cloud accepts.
This error appears in the log file in cases where status_key exceeds 40 characters:
`400: unable to upload Bitbucket status b'{"type": "error", "error": {"message": "key: Ensure this value has at most 40 characters (it has 48).", "fields": {"key": ["Ensure this value has at most 40 characters (it has 48)."]}}}'`

Checking the length for this field might be difficult during configuration checking time, since it might exceed 40 characters only after a certain amount of builds have been done ( e.g. with the value Interpolate("%(prop:buildername)s/%(prop:buildnumber)s") )

I would propose hashing the status_key in order to get predictable key lengths. sha1 happens to be 20 bytes long which is 40 characters in hexadecimal notation.


Discussion:

Resolving patch:
diff --git a/master/buildbot/reporters/bitbucket.py b/master/buildbot/reporters/bitbucket.py
--- a/master/buildbot/reporters/bitbucket.py
+++ b/master/buildbot/reporters/bitbucket.py
@@ -13,6 +13,7 @@
 #
 # Copyright Buildbot Team Members
 
+import hashlib
 from urllib.parse import urlparse
 
 from twisted.internet import defer
@@ -112,9 +113,16 @@ def sendMessage(self, reports):
         props = Properties.fromDict(build['properties'])
         props.master = self.master
 
+        def key_hash(key):
+            sha_obj = hashlib.sha1()
+            sha_obj.update(key.encode('utf-8'))
+            return sha_obj.hexdigest()
+
+        status_key = yield props.render(self.status_key)
+
         body = {
             'state': status,
-            'key': (yield props.render(self.status_key)),
+            'key': key_hash(status_key),
             'name': (yield props.render(self.status_name)),
             'description': reports[0]['subject'],
             'url': build['url']


Test patch:
diff --git a/master/buildbot/test/unit/reporters/test_bitbucket.py b/master/buildbot/test/unit/reporters/test_bitbucket.py
--- a/master/buildbot/test/unit/reporters/test_bitbucket.py
+++ b/master/buildbot/test/unit/reporters/test_bitbucket.py
@@ -73,7 +73,7 @@ def test_basic(self):
             '/user/repo/commit/d34db33fd43db33f/statuses/build',
             json={
                 'state': 'INPROGRESS',
-                'key': 'Builder0',
+                'key': '0550a051225ac4ea91a92c9c94d41dfe6fa9f428',  # sha1("Builder0")
                 'name': 'Builder0',
                 'description': '',
                 'url': 'http://localhost:8080/#/builders/79/builds/0',
@@ -87,7 +87,7 @@ def test_basic(self):
             '/user/repo/commit/d34db33fd43db33f/statuses/build',
             json={
                 'state': 'SUCCESSFUL',
-                'key': 'Builder0',
+                'key': '0550a051225ac4ea91a92c9c94d41dfe6fa9f428',  # sha1("Builder0")
                 'name': 'Builder0',
                 'description': '',
                 'url': 'http://localhost:8080/#/builders/79/builds/0',
@@ -101,7 +101,7 @@ def test_basic(self):
             '/user/repo/commit/d34db33fd43db33f/statuses/build',
             json={
                 'state': 'FAILED',
-                'key': 'Builder0',
+                'key': '0550a051225ac4ea91a92c9c94d41dfe6fa9f428',  # sha1("Builder0")
                 'name': 'Builder0',
                 'description': '',
                 'url': 'http://localhost:8080/#/builders/79/builds/0',
@@ -129,7 +129,7 @@ def test_success_return_codes(self):
             '/user/repo/commit/d34db33fd43db33f/statuses/build',
             json={
                 'state': 'SUCCESSFUL',
-                'key': 'Builder0',
+                'key': '0550a051225ac4ea91a92c9c94d41dfe6fa9f428',  # sha1("Builder0")
                 'name': 'Builder0',
                 'description': '',
                 'url': 'http://localhost:8080/#/builders/79/builds/0',
@@ -148,7 +148,7 @@ def test_success_return_codes(self):
             '/user/repo/commit/d34db33fd43db33f/statuses/build',
             json={
                 'state': 'SUCCESSFUL',
-                'key': 'Builder0',
+                'key': '0550a051225ac4ea91a92c9c94d41dfe6fa9f428',  # sha1("Builder0")
                 'name': 'Builder0',
                 'description': '',
                 'url': 'http://localhost:8080/#/builders/79/builds/0',
@@ -185,7 +185,7 @@ def test_unable_to_send_status(self):
             '/user/repo/commit/d34db33fd43db33f/statuses/build',
             json={
                 'state': 'INPROGRESS',
-                'key': 'Builder0',
+                'key': '0550a051225ac4ea91a92c9c94d41dfe6fa9f428',  # sha1("Builder0")
                 'name': 'Builder0',
                 'description': '',
                 'url': 'http://localhost:8080/#/builders/79/builds/0',
@@ -264,7 +264,7 @@ def test_properties(self):
             '/user/repo/commit/d34db33fd43db33f/statuses/build',
             json={
                 'state': 'INPROGRESS',
-                'key': 'Builder0/0',
+                'key': '84f9e75c46896d56da4fd75e096d24ec62f76f33',  # sha1("Builder0/0")
                 'name': 'Builder0-0',
                 'description': 'not finished build',
                 'url': 'http://localhost:8080/#/builders/79/builds/0',
@@ -278,7 +278,7 @@ def test_properties(self):
             '/user/repo/commit/d34db33fd43db33f/statuses/build',
             json={
                 'state': 'SUCCESSFUL',
-                'key': 'Builder0/0',
+                'key': '84f9e75c46896d56da4fd75e096d24ec62f76f33',  # sha1("Builder0/0")
                 'name': 'Builder0-0',
                 'description': 'Build succeeded!',
                 'url': 'http://localhost:8080/#/builders/79/builds/0',