harbor: Webhook integration fails with "response code is 400"

Expected behavior and actual behavior: Hi all,

Currently we are facing an issue with the webhook integration in Harbor. We want to send outbound messages to our internal Mattermost application. I have set up a Mattermost Incoming Webhook for a private and public channel. For both channels we will see the following error messages in the logs:

Job 'WEBHOOK:0490888e0d6e06a2ba632b98' exit with error: run error: webhook job(target: https://URL/hooks/xxxx) response code is 400

The DEBUG log level is enabled.

Here is a screenshot, of the webhook I set up in Harbor:

image

Steps to reproduce the problem:

  • Set up Incoming webhook in Mattermost
  • Create webhook in a Harbor project with type http
  • Add the Mattermost Webhook URL
  • Select Verify Remote Certificate
  • Pull or push an image from the project

Versions:

  • harbor version: v2.4.0
  • Mattermost: latest version



Are you aware of this or do you know how to fix this issue? Thank you in advance!

Alexander Barth (alexander.barth@mercedes-benz.com) on behalf of Daimler TSS, Provider Information

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 2
  • Comments: 23 (4 by maintainers)

Most upvoted comments

Hi @wy65701436 I think this is not a bug of Harbor, but I (and others as you can see above) would like to have this possibility to send messages to Mattermost. I could think about something in addition to the existing notify types http and slack in the dropdown menu (e.g. mattermost). In our organization thousands of people are using Mattermost and we as a platform team have a lot of customers who would like to have this feature/possibility. Therefore I would like to keep this issue open and hope, this could be something for the v2.7 release.



Alexander Barth (alexander.barth@mercedes-benz.com) on behalf of Mercedes-Benz Tech Innovation GmbH, Provider Information

I am also interested in this. I can create a PR for Mattermost and Discord, if you are willing to merge it and integrate it in one of the next releases.

I thought about this problem a little longer and until this is fixed somehow officially, I tried to create some kind of bridge between Harbor and Mattermost and came up with the script attached. This is more like a skeleton atm, but I think one can get the idea.

You start the skript like this: python3 echo_server.py <port to listen to> <Mattermost Server URL> <api_key> and you have to set your Harbor Endpoint URL to http://<machine the script is running on>:<port to listen to>

It will listen for incoming messages, reformat them and then send them to you Mattermost Server. Yes, I know, no authentication, no SSL, only pull and replication events with more details atm, no nothing, but it is an idea.

It is mostly based on a gist I do not find anymore and the fabulous https://github.com/numberly/matterhook library, so you need to install that first: pip install matterhook

Perhaps someone can build something more sophisticated based on this…

Python message bridge skeleton:
#!/usr/bin/env python3

from http.server import BaseHTTPRequestHandler, HTTPServer
from matterhook import Webhook
from datetime import datetime
import json
import logging

class MatterBridge(BaseHTTPRequestHandler):
    matterserver = ''
    api_key = ''

    def _set_response(self):
        self.send_response(200)
        self.send_header('Content-type', 'text/html')
        self.end_headers()

    def do_GET(self):
        # only for informational purpose
        logging.info("GET request,\nPath: %s\nHeaders:\n%s\n", str(self.path), str(self.headers))
        self._set_response()

    def do_POST(self):
        mwh = Webhook(self.matterserver,self.api_key)
        content_length = int(self.headers['Content-Length']) # <--- Gets the size of data
        post_data = self.rfile.read(content_length) # <--- Gets the data itself

        # Activate logging for every request
        #logging.info("POST request,\nPath: %s\nHeaders:\n%s\n\nBody:\n%s\n",
        #        str(self.path), str(self.headers), post_data.decode('utf-8'))

        # print pure data
        # print(json.dumps(data,indent=4))

        data=json.loads(post_data.decode('utf-8'),strict=False)

        try:
            message=''
            if data['type'] == 'PUSH_ARTIFACT':
                message='Image pushed'
            elif data['type'] == 'PULL_ARTIFACT':
                operator=data['operator']
                occur_at=datetime.strftime(datetime.fromtimestamp(data['occur_at']),"%c")
                image=data['event_data']['repository']['repo_full_name']
                message='Pulled image:\n'
                message+=image+'\n'
                message+='Operator: ' + operator + '\n'
                message+='Event timestamp: ' + occur_at
            elif data['type'] == 'DELETE_ARTIFACT':
                message='Artifact deleted'
            elif data['type'] == 'UPLOAD_CHART':
                message='Chart uploaded'
            elif data['type'] == 'DOWNLOAD_CHART':
                message='Chart downloaded'
            elif data['type'] == 'DELETE_CHART':
                message='Chart deleted'
            elif data['type'] == 'SCANNING_COMPLETED':
                message='Scanning completed'
            elif data['type'] == 'SCANNING_FAILED':
                message='Scanning failed'
            elif data['type'] == 'QUOTA_EXCEED':
                message='Quota exceeded'
            elif data['type'] == 'QUOTA_WARNING':
                message='Quota warning'
            elif data['type'] == 'REPLICATION':
                status=data['event_data']['replication']['job_status']
                if status == 'Success':
                    operator=data['operator']
                    occur_at=datetime.strftime(datetime.fromtimestamp(data['occur_at']),"%c")
                    namespace=data['event_data']['replication']['dest_resource']['namespace']
                    message='Image replicated:\n'
                    message+='Namespace: ' + namespace +'\n'
                    if 'successful_artifact' in data['event_data']['replication']:
                        for part in data['event_data']['replication']['successful_artifact']:
                            message+='Artifact: ' + part['name_tag'] + '\n'
                    message+='Operator: ' + operator + '\n'
                    message+='Status: ' + status + '\n'
                    message+='Event timestamp: ' + occur_at

            if message != '':
                mwh.send(message)

        except:
            # show requests with problems
            logging.info("POST request,\nPath: %s\nHeaders:\n%s\n\nBody:\n%s\n",
                    str(self.path), str(self.headers), post_data.decode('utf-8'))
            # print raw body data
            # print(json.dumps(data, indent=4))

        self._set_response()

def run(server_class=HTTPServer, handler_class=MatterBridge, port=8065, mm='', key=''):
    logging.basicConfig(level=logging.INFO)

    MatterBridge.matterserver = mm
    MatterBridge.api_key = key
    
    server_address = ('', port)
    httpd = server_class(server_address, handler_class)

    logging.info('Starting httpd...\n')
    try:
        httpd.serve_forever()
    except KeyboardInterrupt:
        pass
    httpd.server_close()
    logging.info('Stopping httpd...\n')

if __name__ == '__main__':
    from sys import argv

    if len(argv) < 4:
        print("Harbor to Mattermost Webhook Bridge")
        print("Usage:")
        print("./echo_server.py <port> '<Mattermost URL>' '<API KEY>'")
        print()
        print("Port, Mattermost Server URL, API KEY missing")
    else:
        run(port=int(argv[1]), mm=str(argv[2]), key=str(argv[3]))

The issue still exists and needs to be fixed.