diff --git a/cla-backend-go/package-lock.json b/cla-backend-go/package-lock.json index 5a7f65a65..5e8330669 100644 --- a/cla-backend-go/package-lock.json +++ b/cla-backend-go/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "install": "^0.13.0", "node.extend": "^2.0.2", - "serverless": "^3.32.2", + "serverless": "^3.33.0", "serverless-finch": "^4.0.3", "serverless-layers": "^2.6.1", "serverless-plugin-tracing": "^2.0.0", @@ -743,9 +743,9 @@ } }, "node_modules/aws-sdk": { - "version": "2.1389.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1389.0.tgz", - "integrity": "sha512-H+Q1oZ4bWi5xOp2EabZpbHcQjz55luvn6cUCHtgmMFJPQQGEWlJ9siWTi+6C+/0VyrGambup7aXILhF6prU+Zg==", + "version": "2.1410.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1410.0.tgz", + "integrity": "sha512-EKC7DxVknwVKdZg7yRnH01UM3+K3H4SGfdM1GJ1lgHrWAdgEsMIzYDFceoFT+E7qIg5YHXgpKfpzDEKsI3p8wQ==", "dependencies": { "buffer": "4.9.2", "events": "1.1.1", @@ -1434,10 +1434,9 @@ "license": "ISC" }, "node_modules/dayjs": { - "version": "1.11.7", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz", - "integrity": "sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==", - "license": "MIT" + "version": "1.11.9", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.9.tgz", + "integrity": "sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==" }, "node_modules/debug": { "version": "4.3.4", @@ -1723,9 +1722,9 @@ } }, "node_modules/dotenv": { - "version": "16.1.3", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.1.3.tgz", - "integrity": "sha512-FYssxsmCTtKL72fGBSvb1K9dRz0/VZeWqFme/vSb7r7323x4CRaHu4LvQ5JG3+s6yt2YPbBrkpiEODktfyjI9A==", + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", "engines": { "node": ">=12" }, @@ -4202,9 +4201,9 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, "node_modules/semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -4216,9 +4215,9 @@ } }, "node_modules/serverless": { - "version": "3.32.2", - "resolved": "https://registry.npmjs.org/serverless/-/serverless-3.32.2.tgz", - "integrity": "sha512-OIh0dF8siYI2coGFVXg1iKvkjZXO1g7LXXe2asZe0HDEXENlgLA47zMerz1l3iFWVHsFN0901c+eW8av2W/Uaw==", + "version": "3.33.0", + "resolved": "https://registry.npmjs.org/serverless/-/serverless-3.33.0.tgz", + "integrity": "sha512-qmG0RMelsWmnS5Smxoy0CbjpecgnJlM89wzSIgJqfkGlmOo2nJdd8y0/E6KlaTsaozlPKkjUBDzis2nF8VNO2g==", "hasInstallScript": true, "dependencies": { "@serverless/dashboard-plugin": "^6.2.3", @@ -4227,7 +4226,7 @@ "ajv": "^8.12.0", "ajv-formats": "^2.1.1", "archiver": "^5.3.1", - "aws-sdk": "^2.1389.0", + "aws-sdk": "^2.1404.0", "bluebird": "^3.7.2", "cachedir": "^2.3.0", "chalk": "^4.1.2", @@ -4235,9 +4234,9 @@ "ci-info": "^3.8.0", "cli-progress-footer": "^2.3.2", "d": "^1.0.1", - "dayjs": "^1.11.7", + "dayjs": "^1.11.8", "decompress": "^4.2.1", - "dotenv": "^16.1.3", + "dotenv": "^16.3.1", "dotenv-expand": "^10.0.0", "essentials": "^1.2.0", "ext": "^1.7.0", @@ -4265,7 +4264,7 @@ "process-utils": "^4.0.0", "promise-queue": "^2.2.5", "require-from-string": "^2.0.2", - "semver": "^7.5.1", + "semver": "^7.5.3", "signal-exit": "^3.0.7", "stream-buffers": "^3.0.2", "strip-ansi": "^6.0.1", @@ -5981,9 +5980,9 @@ "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" }, "aws-sdk": { - "version": "2.1389.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1389.0.tgz", - "integrity": "sha512-H+Q1oZ4bWi5xOp2EabZpbHcQjz55luvn6cUCHtgmMFJPQQGEWlJ9siWTi+6C+/0VyrGambup7aXILhF6prU+Zg==", + "version": "2.1410.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1410.0.tgz", + "integrity": "sha512-EKC7DxVknwVKdZg7yRnH01UM3+K3H4SGfdM1GJ1lgHrWAdgEsMIzYDFceoFT+E7qIg5YHXgpKfpzDEKsI3p8wQ==", "requires": { "buffer": "4.9.2", "events": "1.1.1", @@ -6509,9 +6508,9 @@ } }, "dayjs": { - "version": "1.11.7", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz", - "integrity": "sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==" + "version": "1.11.9", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.9.tgz", + "integrity": "sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==" }, "debug": { "version": "4.3.4", @@ -6735,9 +6734,9 @@ } }, "dotenv": { - "version": "16.1.3", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.1.3.tgz", - "integrity": "sha512-FYssxsmCTtKL72fGBSvb1K9dRz0/VZeWqFme/vSb7r7323x4CRaHu4LvQ5JG3+s6yt2YPbBrkpiEODktfyjI9A==" + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==" }, "dotenv-expand": { "version": "10.0.0", @@ -8488,17 +8487,17 @@ } }, "semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", + "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", "requires": { "lru-cache": "^6.0.0" } }, "serverless": { - "version": "3.32.2", - "resolved": "https://registry.npmjs.org/serverless/-/serverless-3.32.2.tgz", - "integrity": "sha512-OIh0dF8siYI2coGFVXg1iKvkjZXO1g7LXXe2asZe0HDEXENlgLA47zMerz1l3iFWVHsFN0901c+eW8av2W/Uaw==", + "version": "3.33.0", + "resolved": "https://registry.npmjs.org/serverless/-/serverless-3.33.0.tgz", + "integrity": "sha512-qmG0RMelsWmnS5Smxoy0CbjpecgnJlM89wzSIgJqfkGlmOo2nJdd8y0/E6KlaTsaozlPKkjUBDzis2nF8VNO2g==", "requires": { "@serverless/dashboard-plugin": "^6.2.3", "@serverless/platform-client": "^4.3.2", @@ -8506,7 +8505,7 @@ "ajv": "^8.12.0", "ajv-formats": "^2.1.1", "archiver": "^5.3.1", - "aws-sdk": "^2.1389.0", + "aws-sdk": "^2.1404.0", "bluebird": "^3.7.2", "cachedir": "^2.3.0", "chalk": "^4.1.2", @@ -8514,9 +8513,9 @@ "ci-info": "^3.8.0", "cli-progress-footer": "^2.3.2", "d": "^1.0.1", - "dayjs": "^1.11.7", + "dayjs": "^1.11.8", "decompress": "^4.2.1", - "dotenv": "^16.1.3", + "dotenv": "^16.3.1", "dotenv-expand": "^10.0.0", "essentials": "^1.2.0", "ext": "^1.7.0", @@ -8544,7 +8543,7 @@ "process-utils": "^4.0.0", "promise-queue": "^2.2.5", "require-from-string": "^2.0.2", - "semver": "^7.5.1", + "semver": "^7.5.3", "signal-exit": "^3.0.7", "stream-buffers": "^3.0.2", "strip-ansi": "^6.0.1", diff --git a/cla-backend-go/package.json b/cla-backend-go/package.json index 3cf26200c..dca563387 100644 --- a/cla-backend-go/package.json +++ b/cla-backend-go/package.json @@ -21,7 +21,7 @@ "dependencies": { "install": "^0.13.0", "node.extend": "^2.0.2", - "serverless": "^3.32.2", + "serverless": "^3.33.0", "serverless-finch": "^4.0.3", "serverless-layers": "^2.6.1", "serverless-plugin-tracing": "^2.0.0", diff --git a/cla-backend-go/yarn.lock b/cla-backend-go/yarn.lock index c752eeb4a..9e06baa77 100644 --- a/cla-backend-go/yarn.lock +++ b/cla-backend-go/yarn.lock @@ -363,7 +363,7 @@ available-typed-arrays@^1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz" -aws-sdk@^2.1329.0, aws-sdk@^2.1389.0: +aws-sdk@^2.1329.0, aws-sdk@^2.1404.0: version "2.1386.0" resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1386.0.tgz#6005b33d6c8e9769268d899b3640695e88ce36fd" dependencies: @@ -793,9 +793,9 @@ d@1, d@^1.0.1: es5-ext "^0.10.50" type "^1.0.1" -dayjs@^1.11.7: - version "1.11.7" - resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz" +dayjs@^1.11.8: + version "1.11.9" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.9.tgz#9ca491933fadd0a60a2c19f6c237c03517d71d1a" debug@4, debug@^4.1.1, debug@^4.3.4: version "4.3.4" @@ -901,9 +901,9 @@ dotenv-expand@^10.0.0: version "10.0.0" resolved "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz" -dotenv@^16.1.3: - version "16.1.3" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.1.3.tgz#0c67e90d0ddb48d08c570888f709b41844928210" +dotenv@^16.3.1: + version "16.3.1" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e" duration@^0.2.2: version "0.2.2" @@ -2321,9 +2321,9 @@ semver@^6.0.0: version "6.3.0" resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" -semver@^7.3.2, semver@^7.3.5, semver@^7.3.8, semver@^7.5.1: - version "7.5.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.1.tgz#c90c4d631cf74720e46b21c1d37ea07edfab91ec" +semver@^7.3.2, semver@^7.3.5, semver@^7.3.8, semver@^7.5.3: + version "7.5.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.3.tgz#161ce8c2c6b4b3bdca6caadc9fa3317a4c4fe88e" dependencies: lru-cache "^6.0.0" @@ -2362,9 +2362,9 @@ serverless-prune-plugin@^2.0.2: dependencies: bluebird "^3.7.2" -serverless@^3.32.2: - version "3.32.2" - resolved "https://registry.yarnpkg.com/serverless/-/serverless-3.32.2.tgz#7d55a44eb2e08cbb8349b9c8c68b747ba4e4a462" +serverless@^3.33.0: + version "3.33.0" + resolved "https://registry.yarnpkg.com/serverless/-/serverless-3.33.0.tgz#7d4aacfacb5f122a24e8c6a8d2972cce99746c0c" dependencies: "@serverless/dashboard-plugin" "^6.2.3" "@serverless/platform-client" "^4.3.2" @@ -2372,7 +2372,7 @@ serverless@^3.32.2: ajv "^8.12.0" ajv-formats "^2.1.1" archiver "^5.3.1" - aws-sdk "^2.1389.0" + aws-sdk "^2.1404.0" bluebird "^3.7.2" cachedir "^2.3.0" chalk "^4.1.2" @@ -2380,9 +2380,9 @@ serverless@^3.32.2: ci-info "^3.8.0" cli-progress-footer "^2.3.2" d "^1.0.1" - dayjs "^1.11.7" + dayjs "^1.11.8" decompress "^4.2.1" - dotenv "^16.1.3" + dotenv "^16.3.1" dotenv-expand "^10.0.0" essentials "^1.2.0" ext "^1.7.0" @@ -2410,7 +2410,7 @@ serverless@^3.32.2: process-utils "^4.0.0" promise-queue "^2.2.5" require-from-string "^2.0.2" - semver "^7.5.1" + semver "^7.5.3" signal-exit "^3.0.7" stream-buffers "^3.0.2" strip-ansi "^6.0.1" diff --git a/cla-backend/cla/controllers/github.py b/cla-backend/cla/controllers/github.py index 9b465b9b5..eb6b76298 100644 --- a/cla-backend/cla/controllers/github.py +++ b/cla-backend/cla/controllers/github.py @@ -284,6 +284,10 @@ def activity(action: str, event_type: str, body: dict): elif event_type == "issue_comment": cla.log.debug(f'{fn} - received issue_comment action: {action}...') handle_pull_request_comment_event(action, body) + + # Github Merge Group Event + elif event_type == 'merge_group': + handle_merge_group_event(action, body) else: cla.log.debug(f'{fn} - ignoring github activity event, action: {action}...') @@ -350,7 +354,7 @@ def handle_pull_request_event(action: str, body: dict): cla.log.debug(f'{func_name} - processing github pull_request activity callback...') # New PR opened - if action == 'opened' or action == 'reopened' or action == 'synchronize' or action == 'enqueued': + if action == 'opened' or action == 'reopened' or action == 'synchronize': cla.log.debug(f'{func_name} - processing github pull_request activity for action: {action}') # Copied from repository_service.py service = cla.utils.get_repository_service('github') @@ -359,6 +363,20 @@ def handle_pull_request_event(action: str, body: dict): else: cla.log.debug(f'{func_name} - ignoring github pull_request activity for action: {action}') +def handle_merge_group_event(action: str, body: dict): + func_name = 'github.activity.handle_merge_group_event' + cla.log.debug(f'{func_name} - processing github merge_group activity callback...') + + # Checks Requested action + if action == 'checks_requested': + cla.log.debug(f'{func_name} - processing github merge_group activity for action: {action}') + # Copied from repository_service.py + service = cla.utils.get_repository_service('github') + result = service.received_activity(body) + return result + else: + cla.log.debug(f'{func_name} - ignoring github merge_group activity for action: {action}') + def handle_pull_request_comment_event(action: str, body: dict): func_name = 'github.activity.handle_pull_request_comment_event' @@ -581,6 +599,7 @@ def notify_project_managers(repositories): f' to managers: {recipients}' f' for project {project} with ' f' repositories: {repositories}') + def unable_to_do_cla_check_email_content(project, managers, repositories): diff --git a/cla-backend/cla/models/docusign_models.py b/cla-backend/cla/models/docusign_models.py index 756335585..8a139f24d 100644 --- a/cla-backend/cla/models/docusign_models.py +++ b/cla-backend/cla/models/docusign_models.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: MIT """ -Easily perform signing workflows using DocuSign signing service with pydocusign. +Easily perform signing workflows using DocuSign signing service with docusign_esign. NOTE: This integration uses DocuSign's Legacy Authentication REST API Integration. https://developers.docusign.com/esign-rest-api/guides/post-go-live @@ -14,30 +14,31 @@ import os import urllib.request import uuid +import base64 import xml.etree.ElementTree as ET from typing import Any, Dict, List, Optional from urllib.parse import urlparse import cla -import pydocusign # type: ignore import requests from attr import dataclass from cla.controllers.lf_group import LFGroup from cla.models import DoesNotExist, signing_service_interface from cla.models.dynamo_models import (Company, Document, Event, Gerrit, Project, Signature, User) +import docusign_esign +from docusign_esign.client.api_exception import ApiException from cla.models.event_types import EventType from cla.models.s3_storage import S3Storage from cla.user_service import UserService from cla.utils import (append_email_help_sign_off_content, get_corporate_url, get_email_help_content, get_project_cla_group_instance) -from pydocusign.exceptions import DocuSignException # type: ignore api_base_url = os.environ.get('CLA_API_BASE', '') root_url = os.environ.get('DOCUSIGN_ROOT_URL', '') -username = os.environ.get('DOCUSIGN_USERNAME', '') -password = os.environ.get('DOCUSIGN_PASSWORD', '') -integrator_key = os.environ.get('DOCUSIGN_INTEGRATOR_KEY', '') +ds_client_id = os.environ.get('DOCUSIGN_CLIENT_ID', '') +ds_user_id = os.environ.get('DOCUSIGN_USER_ID', '') +ds_private_key = os.environ.get('DOCUSIGN_PRIVATE_KEY', '') lf_group_client_url = os.environ.get('LF_GROUP_CLIENT_URL', '') lf_group_client_id = os.environ.get('LF_GROUP_CLIENT_ID', '') @@ -96,35 +97,55 @@ class DocuSign(signing_service_interface.SigningService): 'signed_date': '{http://www.docusign.net/API/3.0}Signed', } + SCOPES = [ + "signature", "impersonation" + ] def __init__(self): - self.client = None + self.ds_access_token = "" + self.ds_base_url = "" + self.ds_account_id = "" self.s3storage = None def initialize(self, config): - self.client = pydocusign.DocuSignClient(root_url=root_url, - username=username, - password=password, - integrator_key=integrator_key) + api_client = docusign_esign.ApiClient() + api_client.set_base_path(root_url) + api_client.set_oauth_host_name(root_url) + err = self.setAuthToken() + if err != None: + return err + + self.s3storage = S3Storage() + self.s3storage.initialize(None) + def setAuthToken(self): + api_client = docusign_esign.ApiClient() + api_client.set_base_path(root_url) + response = api_client.request_jwt_user_token( + client_id=ds_client_id, + user_id=ds_user_id, + oauth_host_name=root_url, + private_key_bytes=ds_private_key, + expires_in=4000, + scopes=self.SCOPES + ) + self.ds_access_token = response.access_token try: - login_data = self.client.login_information() - login_account = login_data['loginAccounts'][0] - base_url = login_account['baseUrl'] - account_id = login_account['accountId'] - url = urlparse(base_url) - parsed_root_url = '{}://{}/restapi/v2'.format(url.scheme, url.netloc) + user_info = api_client.get_user_info(self.ds_access_token) + accounts = user_info.get_accounts() + self.ds_base_url = accounts[0].base_uri + "/restapi" + self.ds_account_id = accounts[0].account_id except Exception as e: cla.log.error('Error logging in to DocuSign: {}'.format(e)) return {'errors': {'Error initializing DocuSign'}} + return None - self.client = pydocusign.DocuSignClient(root_url=parsed_root_url, - account_url=base_url, - account_id=account_id, - username=username, - password=password, - integrator_key=integrator_key) - self.s3storage = S3Storage() - self.s3storage.initialize(None) + def get_api_client(self): + """Create api client and construct API headers""" + api_client = docusign_esign.ApiClient() + api_client.host = self.ds_base_url + api_client.set_default_header(header_name="Authorization", header_value=f"Bearer {self.ds_access_token}") + + return api_client def request_individual_signature(self, project_id, user_id, return_url=None, return_url_type="github", callback_url=None, preferred_email=None): @@ -1293,7 +1314,16 @@ def populate_sign_url(self, signature, callback_url=None, f'for project {project.get_project_name()} expired. A new session will be in place for ' 'your signing process.') cla.log.debug(message) - self.client.void_envelope(envelope_id, message) + env = docusign_esign.Envelope() + env.status = 'voided' + env.voided_reason = message + api_client = self.get_api_client() + envelope_api = docusign_esign.EnvelopesApi(api_client) + envelope_api.update( + account_id=self.ds_account_id, + envelope_id=envelope_id, + envelope=env + ) except Exception as e: cla.log.warning(f'{fn} - {sig_type} - DocuSign error while voiding the envelope - ' f'regardless, continuing on..., error: {e}') @@ -1333,13 +1363,15 @@ def populate_sign_url(self, signature, callback_url=None, project_names=project_names)) cla.log.debug(f'populate_sign_url - {sig_type} - generating a docusign signer object form email with' f'name: {signatory_name}, email: {signatory_email}, subject: {email_subject}') - signer = pydocusign.Signer(email=signatory_email, + signer = docusign_esign.Signer(email=signatory_email, name=signatory_name, - recipientId=1, + recipient_id=1, tabs=tabs, - emailSubject=email_subject, - emailBody=email_body, - supportedLanguage='en', + email_notification = { + 'emailBody': email_body, + 'emailSubject': email_subject, + 'supportedLanguage': 'en', + } ) else: # This will be the Initial CLA Manager @@ -1364,12 +1396,14 @@ def populate_sign_url(self, signature, callback_url=None, user_identifier = signatory_email else: user_identifier = signatory_name - signer = pydocusign.Signer(email=signatory_email, name=signatory_name, - recipientId=1, clientUserId=signature.get_signature_id(), + signer = docusign_esign.Signer(email=signatory_email, name=signatory_name, + recipient_id=1, client_user_id=signature.get_signature_id(), tabs=tabs, - emailSubject=email_subject, - emailBody='CLA Sign Request for {}'.format(user_identifier), - supportedLanguage='en', + email_notification = { + 'emailBody': 'CLA Sign Request for {}'.format(user_identifier), + 'emailSubject': email_subject, + 'supportedLanguage': 'en', + } ) content_type = document.get_document_content_type() @@ -1381,53 +1415,53 @@ def populate_sign_url(self, signature, callback_url=None, else: content = document.get_document_content() pdf = io.BytesIO(content) - + content_bytes = pdf.read() + base64_file_content = base64.b64encode(content_bytes).decode("ascii") doc_name = document.get_document_name() cla.log.debug(f'{fn} - {sig_type} - docusign document ' f'name: {doc_name}, id: {document_id}, content type: {content_type}') - document = pydocusign.Document(name=doc_name, documentId=document_id, data=pdf) + document = docusign_esign.Document(name=doc_name, document_id=document_id, document_base64=base64_file_content, file_extension="pdf") if callback_url is not None: # Webhook properties for callbacks after the user signs the document. # Ensure that a webhook is returned on the status "Completed" where # all signers on a document finish signing the document. recipient_events = [{"recipientEventStatusCode": "Completed"}] - event_notification = pydocusign.EventNotification(url=callback_url, - loggingEnabled=True, - recipientEvents=recipient_events) - envelope = pydocusign.Envelope( + event_notification = docusign_esign.EventNotification(url=callback_url, + logging_enabled=True, + recipient_events=recipient_events) + envelope = docusign_esign.EnvelopeDefinition( documents=[document], - emailSubject=f'EasyCLA: CLA Signature Request for {project.get_project_name()}', - emailBlurb='CLA Sign Request', - eventNotification=event_notification, - status=pydocusign.Envelope.STATUS_SENT, + email_subject=f'EasyCLA: CLA Signature Request for {project.get_project_name()}', + email_blurb='CLA Sign Request', + event_notification=event_notification, + status="sent", recipients=[signer]) else: - envelope = pydocusign.Envelope( + envelope = docusign_esign.EnvelopeDefinition( documents=[document], - emailSubject=f'EasyCLA: CLA Signature Request for {project.get_project_name()}', - emailBlurb='CLA Sign Request', - status=pydocusign.Envelope.STATUS_SENT, + email_subject=f'EasyCLA: CLA Signature Request for {project.get_project_name()}', + email_blurb='CLA Sign Request', + status="sent", recipients=[signer]) - - envelope = self.prepare_sign_request(envelope) + envelope_result = self.prepare_sign_request(envelope) if not send_as_email: recipient = envelope.recipients[0] # The URL the user will be redirected to after signing. # This route will be in charge of extracting the signature's return_url and redirecting. - return_url = os.path.join(api_base_url, 'v2/return-url', str(recipient.clientUserId)) + return_url = os.path.join(api_base_url, 'v2/return-url', str(recipient.client_user_id)) cla.log.debug(f'populate_sign_url - {sig_type} - generating signature sign_url, ' f'using return-url as: {return_url}') - sign_url = self.get_sign_url(envelope, recipient, return_url) + sign_url = self.get_sign_url(envelope_result.envelope_id, recipient, return_url) cla.log.debug(f'populate_sign_url - {sig_type} - setting signature sign_url as: {sign_url}') signature.set_signature_sign_url(sign_url) # Save Envelope ID in signature. cla.log.debug(f'{fn} - {sig_type} - saving signature to database...') - signature.set_signature_envelope_id(envelope.envelopeId) + signature.set_signature_envelope_id(envelope_result.envelope_id) signature.save() cla.log.debug(f'{fn} - {sig_type} - saved signature to database - id: {signature.get_signature_id()}...') cla.log.debug(f'populate_sign_url - {sig_type} - complete') @@ -1897,11 +1931,11 @@ def get_signed_document(self, envelope_id, user): fn = 'models.docusign_models.get_signed_document' cla.log.debug(f'{fn} - fetching signed CLA document for envelope: {envelope_id}') - envelope = pydocusign.Envelope() - envelope.envelopeId = envelope_id - + try: - documents = envelope.get_document_list(self.client) + api_client = self.get_api_client() + envelope_api = docusign_esign.EnvelopesApi(api_client) + documents = envelope_api.list_documents(account_id=self.ds_account_id, envelope_id=envelope_id) except Exception as err: cla.log.error(f'{fn} - unknown error when trying to load signed document: {err}') return @@ -1912,17 +1946,17 @@ def get_signed_document(self, envelope_id, user): return document = documents[0] - if 'documentId' not in document: + if 'document_id' not in document: cla.log.error(f'{fn} - not document ID found in document response: {document}') return try: # TODO: Also send the signature certificate? envelope.get_certificate() - document_file = envelope.get_document(document['documentId'], self.client) + document_file = envelope_api.get_document(account_id=self.ds_account_id,document_id=document['document_id'],envelope_id=envelope_id) return document_file.read() except Exception as err: cla.log.error('{fn} - unknown error when trying to fetch signed document content ' - f'for document ID {document["documentId"]}, error: {err}') + f'for document ID {document["document_id"]}, error: {err}') return def send_signed_document(self, signature, document_data, user, icla=True): @@ -1969,37 +2003,51 @@ def get_document_resource(self, url): # pylint: disable=no-self-use """ return urllib.request.urlopen(url) - def prepare_sign_request(self, envelope): + def prepare_sign_request(self, envelope_definition): """ Mockable method for sending a signature request to DocuSign. :param envelope: The envelope to send to DocuSign. - :type envelope: pydocusign.Envelope + :type envelope: docusign_esign.Envelope :return: The new envelope to work with after the request has been sent. - :rtype: pydocusign.Envelope + :rtype: docusign_esign.Envelope """ try: - self.client.create_envelope_from_documents(envelope) - envelope.get_recipients() - return envelope - except DocuSignException as err: + api_client = self.get_api_client() + envelope_api = docusign_esign.EnvelopesApi(api_client) + result = envelope_api.create_envelope(account_id=self.ds_account_id, envelope_definition=envelope_definition) + return result + except ApiException as err: cla.log.error(f'prepare_sign_request - error while fetching DocuSign envelope recipients: {err}') - def get_sign_url(self, envelope, recipient, return_url): # pylint:disable=no-self-use + def get_sign_url(self, envelope_id, recipient, return_url): # pylint:disable=no-self-use """ Mockable method for getting a signing url. :param envelope: The envelope in question. - :type envelope: pydocusign.Envelope + :type envelope: docusign_esign.Envelope :param recipient: The recipient inside this envelope. - :type recipient: pydocusign.Recipient + :type recipient: docusign_esign.Recipient :param return_url: The URL to return the user after successful signing. :type return_url: string :return: A URL for the recipient to hit for signing. :rtype: string """ - return envelope.post_recipient_view(recipient, returnUrl=return_url) - + recipient_view_request = docusign_esign.RecipientViewRequest( + authentication_method="None", + client_user_id=recipient.client_user_id, + recipient_id="1", + return_url=return_url, + user_name=recipient.name, + email=recipient.email + ) + api_client = self.get_api_client() + envelope_api = docusign_esign.EnvelopesApi(api_client) + return envelope_api.create_recipient_view( + account_id=self.ds_account_id, + envelope_id=envelope_id, + recipient_view_request=recipient_view_request + ) class MockDocuSign(DocuSign): """ @@ -2083,29 +2131,29 @@ def get_docusign_tabs_from_document(document: Document, :type document: cla.models.model_interfaces.Document :param document_id: The ID of the document to use for grouping of the tabs. :type document_id: int - :return: List of formatted tabs for consumption by pydocusign. - :rtype: [pydocusign.Tab] + :return: List of formatted tabs for consumption by docusign_esign. + :rtype: [docusign_esign.Tabs] """ tabs = [] for tab in document.get_document_tabs(): args = { - 'documentId': document_id, - 'pageNumber': tab.get_document_tab_page(), - 'xPosition': tab.get_document_tab_position_x(), - 'yPosition': tab.get_document_tab_position_y(), + 'document_id': document_id, + 'page_number': tab.get_document_tab_page(), + 'x_position': tab.get_document_tab_position_x(), + 'y_position': tab.get_document_tab_position_y(), 'width': tab.get_document_tab_width(), 'height': tab.get_document_tab_height(), - 'customTabId': tab.get_document_tab_id(), - 'tabLabel': tab.get_document_tab_id(), + 'custom_tab_id': tab.get_document_tab_id(), + 'tab_label': tab.get_document_tab_id(), 'name': tab.get_document_tab_name() } if tab.get_document_tab_anchor_string() is not None: # Set only when anchor string exists - args['anchorString'] = tab.get_document_tab_anchor_string() - args['anchorIgnoreIfNotPresent'] = tab.get_document_tab_anchor_ignore_if_not_present() - args['anchorXOffset'] = tab.get_document_tab_anchor_x_offset() - args['anchorYOffset'] = tab.get_document_tab_anchor_y_offset() + args['anchor_string'] = tab.get_document_tab_anchor_string() + args['anchor_ignore_if_not_present'] = tab.get_document_tab_anchor_ignore_if_not_present() + args['anchor_x_offset'] = tab.get_document_tab_anchor_x_offset() + args['anchor_y_offset'] = tab.get_document_tab_anchor_y_offset() # Remove x,y coordinates since offsets will define them # del args['xPosition'] # del args['yPosition'] @@ -2116,27 +2164,27 @@ def get_docusign_tabs_from_document(document: Document, tab_type = tab.get_document_tab_type() if tab_type == 'text': - tab_class = pydocusign.TextTab + tab_class = docusign_esign.Text elif tab_type == 'text_unlocked': - tab_class = TextUnlockedTab - args['locked'] = False + tab_class = docusign_esign.Text + args['locked'] = "false" elif tab_type == 'text_optional': - tab_class = TextOptionalTab + tab_class = docusign_esign.Text # https://developers.docusign.com/docs/esign-rest-api/reference/envelopes/enveloperecipienttabs/create/#schema__enveloperecipienttabs_texttabs_required # required: string - When true, the signer is required to fill out this tab. - args['required'] = False + args['required'] = "false" elif tab_type == 'number': - tab_class = pydocusign.NumberTab + tab_class = docusign_esign.Number elif tab_type == 'sign': - tab_class = pydocusign.SignHereTab + tab_class = docusign_esign.SignHere elif tab_type == 'sign_optional': - tab_class = pydocusign.SignHereTab + tab_class = docusign_esign.SignHere # https://developers.docusign.com/docs/esign-rest-api/reference/envelopes/enveloperecipienttabs/create/#schema__enveloperecipienttabs_signheretabs_optional # optional: string - When true, the recipient does not need to complete this tab to # complete the signing process. args['optional'] = True elif tab_type == 'date': - tab_class = pydocusign.DateSignedTab + tab_class = docusign_esign.DateSigned else: cla.log.warning('Invalid tab type specified (%s) in document file ID %s', tab_type, document.get_document_file_id()) @@ -2298,34 +2346,6 @@ def create_default_individual_values(user: User, preferred_email: str = None) -> return values - -class TextOptionalTab(pydocusign.Tab): - """Tab to show a free-form text field on the document. - """ - attributes = pydocusign.Tab._common_attributes + pydocusign.Tab._formatting_attributes + [ - 'name', - 'value', - 'height', - 'width', - 'locked', - 'required' - ] - tabs_name = 'textTabs' - - -class TextUnlockedTab(pydocusign.Tab): - """Tab to show a free-form text field on the document. - """ - attributes = pydocusign.Tab._common_attributes + pydocusign.Tab._formatting_attributes + [ - 'name', - 'value', - 'height', - 'width', - 'locked' - ] - tabs_name = 'textTabs' - - # managers and contributors are tuples of (name, email) def generate_manager_and_contributor_list(managers, contributors=None): lines = [] diff --git a/cla-backend/cla/models/github_models.py b/cla-backend/cla/models/github_models.py index d06ed66e3..a72876c78 100644 --- a/cla-backend/cla/models/github_models.py +++ b/cla-backend/cla/models/github_models.py @@ -68,15 +68,12 @@ def get_repository_id(self, repo_name, installation_id=None): def received_activity(self, data): cla.log.debug('github_models.received_activity - Received GitHub activity: %s', data) - if 'pull_request' not in data: + if 'pull_request' not in data or 'merge_group' not in data: cla.log.debug('github_models.received_activity - Activity not related to pull request - ignoring') - return {'message': 'Not a pull request - no action performed'} + return {'message': 'Not a pull request nor a merge group - no action performed'} if data['action'] == 'opened': cla.log.debug('github_models.received_activity - Handling opened pull request') return self.process_opened_pull_request(data) - elif data['action'] == 'enqueued': - cla.log.debug('github_models.received_activity - Handling enqueued pull request') - return self.process_opened_pull_request(data) elif data['action'] == 'reopened': cla.log.debug('github_models.received_activity - Handling reopened pull request') return self.process_reopened_pull_request(data) @@ -86,6 +83,9 @@ def received_activity(self, data): elif data['action'] == 'synchronize': cla.log.debug('github_models.received_activity - Handling synchronized pull request') return self.process_synchronized_pull_request(data) + elif data['action'] == 'checks_requested': + cla.log.debug('github_models.received_activity - Handling checks requested pull request') + return self.process_checks_requested_merge_group(data) else: cla.log.debug('github_models.received_activity - Ignoring unsupported action: {}'.format(data['action'])) @@ -326,6 +326,22 @@ def process_opened_pull_request(self, data): github_repository_id = data['repository']['id'] installation_id = data['installation']['id'] self.update_change_request(installation_id, github_repository_id, pull_request_id) + + def process_checks_requested_merge_group(self, data): + """ + Helper method to handle a webhook fired from GitHub for a merge group event. + + :param data: The data returned from GitHub on this webhook. + :type data: dict + """ + merge_group_sha = data['merge_group']['head_sha'] + github_repository_id = data['repository']['id'] + installation_id = data['installation']['id'] + pull_request_message = data['merge_group']['head_commit']['message'] + + # Extract the pull request number from the message + pull_request_id = cla.utils.extract_pull_request_number(pull_request_message) + self.update_merge_group(installation_id, github_repository_id, merge_group_sha, pull_request_id) def process_easycla_command_comment(self, data): """ @@ -363,7 +379,128 @@ def process_easycla_command_comment(self, data): def get_return_url(self, github_repository_id, change_request_id, installation_id): pull_request = self.get_pull_request(github_repository_id, change_request_id, installation_id) return pull_request.html_url + + def get_existing_repository(self, installation_id, github_repository_id): + fn = 'get_existing_repository' + # Queries GH for the complete repository details, see: + # https://developer.github.com/v3/repos/#get-a-repository + cla.log.debug(f'{fn} - fetching repository details for GH repo ID: {github_repository_id}...') + repository = cla.utils.get_repository_by_external_id(installation_id, github_repository_id) + if repository is None: + cla.log.warning(f'{fn} - unable to locate repository by GH ID: {github_repository_id}') + return None + + if repository.get_enabled() is False: + cla.log.warning(f'{fn} - repository is disabled, skipping: {github_repository_id}') + return None + + cla.log.debug(f'{fn} - found repository by GH ID: {github_repository_id}') + return repository + + + def check_org_validity(self, installation_id, repository): + fn = 'check_org_validity' + organization_name = repository.get_organization_name() + + # Check that the Github Organization exists in our database + cla.log.debug(f'{fn} - fetching organization details for GH org name: {organization_name}...') + github_org = GitHubOrg() + try: + github_org.load(organization_name=organization_name) + except DoesNotExist as err: + cla.log.warning(f'{fn} - unable to locate organization by GH name: {organization_name}') + return False + + if github_org.get_organization_installation_id() != installation_id: + cla.log.warning(f'{fn} - ' + f'the installation ID: {github_org.get_organization_installation_id()} ' + f'of this organization does not match installation ID: {installation_id} ' + 'given by the pull request.') + return False + + cla.log.debug(f'{fn} - found organization by GH name: {organization_name}') + return True + + def get_pull_request_retry(self, github_repository_id, change_request_id, installation_id, retries=3) -> dict: + """ + Helper function to retry getting a pull request from GitHub. + """ + fn = 'get_pull_request_retry' + pull_request = {} + for i in range(retries): + try: + # check if change_request_id is a valid int + _ = int(change_request_id) + pull_request = self.get_pull_request(github_repository_id, change_request_id, installation_id) + except ValueError as ve: + cla.log.error(f'{fn} - Invalid PR: {change_request_id} - error: {ve}. Unable to fetch ' + f'PR {change_request_id} from GitHub repository {github_repository_id} ' + f'using installation id {installation_id}.') + if i <= retries: + cla.log.debug(f'{fn} - attempt {i + 1} - waiting to retry...') + time.sleep(2) + continue + else: + cla.log.warning(f'{fn} - attempt {i + 1} - exhausted retries - unable to load PR ' + f'{change_request_id} from GitHub repository {github_repository_id} ' + f'using installation id {installation_id}.') + # TODO: DAD - possibly update the PR status? + return + # Fell through - no error, exit loop and continue on + break + cla.log.debug(f'{fn} - retrieved pull request: {pull_request}') + + return pull_request + + + def update_merge_group(self, installation_id, github_repository_id, merge_group_sha, pull_request_id): + fn = 'update_queue_entry' + + # Note: late 2021/early 2022 we observed that sometimes we get the event for a PR, then go back to GitHub + # to query for the PR details and discover the PR is 404, not available for some reason. Added retry + # logic to retry a couple of times to address any timing issues. + + # Get the pull request details from GitHub + cla.log.debug(f'{fn} - fetching pull request details for GH repo ID: {github_repository_id} ' + f'PR ID: {pull_request_id}...') + pull_request = self.get_pull_request_retry(installation_id, github_repository_id, pull_request_id, installation_id, retries=3) + + # Get Commit authors + commit_authors = self.get_pull_request_commit_authors(pull_request, installation_id) + + try: + repository = self.get_existing_repository(installation_id, github_repository_id) + if repository is None: + cla.log.warning(f'{fn} - unable to locate repository by GH ID: {github_repository_id}') + return None + is_valid = self.check_org_validity(installation_id, repository) + if not is_valid: + cla.log.warning(f'{fn} - the organization is not valid, skipping: {github_repository_id}') + return None + + except DoesNotExist as err: + cla.log.warning(f'{fn} - unable to locate repository by GH ID: {github_repository_id}') + return + + project_id = repository.get_project_id() + cla.log.debug(f'{fn} - found project by GH ID: {github_repository_id}') + project = get_project_instance() + project.load(project_id) + + signed = [] + missing = [] + + # Check if the user has signed the CLA + cla.log.debug(f'{fn} - checking if the user has signed the CLA...') + for user_commit_summary in commit_authors: + handle_commit_from_user(user_commit_summary, signed, missing, project) + + #update Merge group status + update_merge_group_status(installation_id, github_repository_id, merge_group_sha, signed, missing, project.get_version()) + + + def update_change_request(self, installation_id, github_repository_id, change_request_id): fn = 'update_change_request' # Queries GH for the complete pull request details, see: @@ -947,6 +1084,51 @@ def handle_commit_from_user(project, user_commit_summary: UserCommitSummary, sig missing.append(user_commit_summary) +def get_merge_group_commit_authors(merge_group_sha, installation_id=None) -> List[UserCommitSummary]: + """ + Helper function to extract all committer information for a GitHub merge group. + + :param: merge_group_sha: A GitHub merge group sha to examine. + :type: merge_group_sha: string + :return: A list of User Commit Summary objects containing the commit sha and available user information + """ + + fn = 'cla.models.github_models.get_merge_group_commit_authors' + cla.log.debug(f'Querying merge group {merge_group_sha} for commit authors...') + commit_authors = [] + try: + g = cla.utils.get_github_integration_instance(installation_id=installation_id) + commit = g.get_commit(merge_group_sha) + for parent in commit.parents: + try: + cla.log.debug(f'{fn} - Querying parent commit {parent.sha} for commit authors...') + commit = g.get_commit(parent.sha) + cla.log.debug(f'{fn} - Found {commit.commit.author.name} as the author of parent commit {parent.sha}') + commit_authors.append(UserCommitSummary( + parent.sha, + commit.author.id, + commit.author.login, + commit.author.name, + commit.author.email, + False, False + )) + except (GithubException, IncompletableObject) as e: + cla.log.warning(f'{fn} - Unable to query parent commit {parent.sha} for commit authors: {e}') + commit_authors.append(UserCommitSummary( + parent.sha, + None, + None, + None, + None, + False, False + )) + + except Exception as e: + cla.log.warning(f'{fn} - Unable to query merge group {merge_group_sha} for commit authors: {e}') + + return commit_authors + + def get_pull_request_commit_authors(pull_request, installation_id=None) -> List[UserCommitSummary]: """ @@ -1090,6 +1272,35 @@ def has_check_previously_passed_or_failed(pull_request: PullRequest): return True, comment return False, None +def update_merge_group_status(installation_id, repository_id, pull_request,merge_commit_sha,signed, missing, project_version): + """ + Helper function to update a merge queue entrys status based on the list of signers. + :param installation_id: The ID of the GitHub installation + :type installation_id: int + :param repository_id: The ID of the GitHub repository this PR belongs to. + :type repository_id: int + :param pull_request: The GitHub PullRequest object for this PR. + """ + fn = 'update_merge_group_status' + context_name = os.environ.get('GH_STATUS_CTX_NAME') + if context_name is None: + context_name = 'communitybridge/cla' + if missing is not None and len(missing) > 0: + state = 'failure' + context, body = cla.utils.assemble_cla_status(context_name, signed=False) + sign_url = cla.utils.get_full_sign_url( + 'github', str(installation_id), repository_id, pull_request.number, project_version) + cla.log.debug(f'{fn} - Creating new CLA \'{state}\' status - {len(signed)} passed, {missing} failed, ' + f'signing url: {sign_url}') + create_commit_status_for_merge_group(installation_id, repository_id,merge_commit_sha, state, sign_url, body, context) + else: + state = 'success' + context, body = cla.utils.assemble_cla_status(context_name, signed=True) + cla.log.debug(f'{fn} - Creating new CLA \'{state}\' status - {len(signed)} passed, {missing} failed') + create_commit_status_for_merge_group(installation_id, repository_id,merge_commit_sha, state, sign_url, body, context) + + + def update_pull_request(installation_id, github_repository_id, pull_request, repository_name, signed: List[UserCommitSummary], missing: List[UserCommitSummary], project_version): # pylint: disable=too-many-locals @@ -1221,6 +1432,34 @@ def update_pull_request(installation_id, github_repository_id, pull_request, rep create_commit_status(pull_request, last_commit.sha, state, sign_url, body, context) +def create_commit_status_for_merge_group(installation_id, repository_id,merge_commit_sha, state, sign_url, body, context): + """ + Helper function to create a pull request commit status message. + + :param repository_id: The GitHub repository ID. + :type repository_id: string + :param merge_commit_sha: The commit hash to post a status on. + :type merge_commit_sha: string + :param state: The state of the status. + :type state: string + :param sign_url: The link the user will be taken to when clicking on the status message. + :type sign_url: string + :param body: The contents of the status message. + :type body: string + """ + try: + client = GitHubInstallation(installation_id) + repository = client.get_repository(repository_id) + # Get Commit object + commit_obj = repository.get_commit(merge_commit_sha) + # Create status + cla.log.debug(f'Creating commit status for repository {repository_id} and merge commit {merge_commit_sha}') + commit_obj.create_status(state=state, target_url=sign_url, description=body, context=context) + + except Exception as e: + cla.log.warning(f'Unable to create commit status for repository {repository_id} ' + f'and merge commit {merge_commit_sha}: {e}') + def create_commit_status(pull_request, commit_hash, state, sign_url, body, context): """ Helper function to create a pull request commit status message given the PR and commit hash. diff --git a/cla-backend/cla/utils.py b/cla-backend/cla/utils.py index 949b6899d..09ffcc8df 100644 --- a/cla-backend/cla/utils.py +++ b/cla-backend/cla/utils.py @@ -1845,4 +1845,22 @@ def get_co_authors_from_commit(commit): co_authors = re.findall(r"Co-authored-by: (.*) <(.*)>", commit_message) return co_authors +def extract_pull_request_number(pull_request_message): + """ + Helper function to return pull request number from pull request message + :param pull_request_message: message in merge_group payload + :return: + """ + fn = "extract_pull_request_number" + pull_request_number = None + try: + pull_request_number = int(re.search(r"#(\d+)", pull_request_message).group(1)) + except AttributeError as e: + cla.log.warning(f'{fn} - unable to extract pull request number from message: {pull_request_message}, error: {e}') + except Exception as e: + cla.log.warning(f'{fn} - unable to extract pull request number from message: {pull_request_message}, error: {e}') + return pull_request_number + +def get_pull_request() + diff --git a/cla-backend/package.json b/cla-backend/package.json index ac55d473a..c217567cc 100644 --- a/cla-backend/package.json +++ b/cla-backend/package.json @@ -35,7 +35,7 @@ "dependencies": { "install": "^0.13.0", "node.extend": "^2.0.2", - "serverless": "^3.32.2", + "serverless": "^3.33.0", "serverless-domain-manager": "^7.0.4", "serverless-finch": "^4.0.3", "serverless-layers": "^2.6.1", diff --git a/cla-backend/requirements.txt b/cla-backend/requirements.txt index f14cee204..1c09e4eb8 100644 --- a/cla-backend/requirements.txt +++ b/cla-backend/requirements.txt @@ -33,7 +33,7 @@ packaging==20.5 pluggy==0.13.1 py==1.10.0 pyasn1==0.4.8 -pydocusign==2.2 +docusign-esign==3.23.0 PyGithub==1.55 PyJWT==2.7.0 pylint==1.5.2 diff --git a/cla-backend/yarn.lock b/cla-backend/yarn.lock index 2377dc276..831ea778c 100644 --- a/cla-backend/yarn.lock +++ b/cla-backend/yarn.lock @@ -1545,7 +1545,7 @@ available-typed-arrays@^1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz" -aws-sdk@^2.1329.0, aws-sdk@^2.1389.0: +aws-sdk@^2.1329.0, aws-sdk@^2.1404.0: version "2.1386.0" resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1386.0.tgz#6005b33d6c8e9769268d899b3640695e88ce36fd" dependencies: @@ -1980,9 +1980,9 @@ d@1, d@^1.0.1: es5-ext "^0.10.50" type "^1.0.1" -dayjs@^1.11.7: - version "1.11.7" - resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz" +dayjs@^1.11.8: + version "1.11.9" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.9.tgz#9ca491933fadd0a60a2c19f6c237c03517d71d1a" debug@4, debug@^4.1.1, debug@^4.3.4: version "4.3.4" @@ -2092,9 +2092,9 @@ dotenv-expand@^10.0.0: version "10.0.0" resolved "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz" -dotenv@^16.1.3: - version "16.1.3" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.1.3.tgz#0c67e90d0ddb48d08c570888f709b41844928210" +dotenv@^16.3.1: + version "16.3.1" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e" duration@^0.2.2: version "0.2.2" @@ -3577,9 +3577,9 @@ semver@^6.0.0: version "6.3.0" resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" -semver@^7.3.2, semver@^7.3.5, semver@^7.3.8, semver@^7.5.1: - version "7.5.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.1.tgz#c90c4d631cf74720e46b21c1d37ea07edfab91ec" +semver@^7.3.2, semver@^7.3.5, semver@^7.3.8, semver@^7.5.3: + version "7.5.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.3.tgz#161ce8c2c6b4b3bdca6caadc9fa3317a4c4fe88e" dependencies: lru-cache "^6.0.0" @@ -3663,9 +3663,9 @@ serverless-wsgi@^3.0.1: lodash "^4.17.21" process-utils "^4.0.0" -serverless@^3.32.2: - version "3.32.2" - resolved "https://registry.yarnpkg.com/serverless/-/serverless-3.32.2.tgz#7d55a44eb2e08cbb8349b9c8c68b747ba4e4a462" +serverless@^3.33.0: + version "3.33.0" + resolved "https://registry.yarnpkg.com/serverless/-/serverless-3.33.0.tgz#7d4aacfacb5f122a24e8c6a8d2972cce99746c0c" dependencies: "@serverless/dashboard-plugin" "^6.2.3" "@serverless/platform-client" "^4.3.2" @@ -3673,7 +3673,7 @@ serverless@^3.32.2: ajv "^8.12.0" ajv-formats "^2.1.1" archiver "^5.3.1" - aws-sdk "^2.1389.0" + aws-sdk "^2.1404.0" bluebird "^3.7.2" cachedir "^2.3.0" chalk "^4.1.2" @@ -3681,9 +3681,9 @@ serverless@^3.32.2: ci-info "^3.8.0" cli-progress-footer "^2.3.2" d "^1.0.1" - dayjs "^1.11.7" + dayjs "^1.11.8" decompress "^4.2.1" - dotenv "^16.1.3" + dotenv "^16.3.1" dotenv-expand "^10.0.0" essentials "^1.2.0" ext "^1.7.0" @@ -3711,7 +3711,7 @@ serverless@^3.32.2: process-utils "^4.0.0" promise-queue "^2.2.5" require-from-string "^2.0.2" - semver "^7.5.1" + semver "^7.5.3" signal-exit "^3.0.7" stream-buffers "^3.0.2" strip-ansi "^6.0.1"